jvm总结
2023-03-25 22:04:03 2 举报
AI智能生成
jvm相关技术总结
作者其他创作
大纲/内容
内存
内存溢出
堆内存溢出
内存溢出
堆内存剩余空间小于该对象需要分配的空间
内存泄露
对象一直没有被垃圾回收,造成可用的堆内存越来越少
栈内存溢出
1.栈内存中栈帧过多,栈帧的总内存和超过了当前线程的栈内存大小
2.线程分配过度,导致总的栈内存不足以给下一个线程分配栈内存;
方法区+运行时常量池内存溢出
1.方法区
存储的字节码文件大小超过了方法区内存大小
动态创建了大量java类,这些类需要被存储到方法区,导致方法区内存不够
2.常量池内存溢出
程序中动态的创建了大量的基础数据类型和字符串,导致常量池不够分配新创建的常量
本机内存溢出
频繁调用本地方法,创建对象导致本机直接内存不够分配
对象内存分配原则
对象优先在eden区域分配
对象优先在eden区域分配内存
eden区域内存满了,再存放from suvivor区域
长期存活对象进入老年代
新生代对象经历过一定的垃圾回收次数还存活,就会被放到老年代内存中去;
大对象直接进入老年代
内存大小超过一定大小的对象直接放入老年代
相同年纪大小的对象的内存综合超过survivor区域内存一半,那么大于这个年纪的对象都会放在老年代
内存分配担保原则
新生代每一次gc之后,都有可能把存储不下的对象放在老年代;所以老年代会留出一些空间给这些新生代
老年代每次测量进入老年代对象的年纪大小,去做评估预测,老年代剩余空间是否能足够装下下次进入老年代的新生代对象,如果不够,就会做老年代的垃圾回收
java内存模型
内存模型概述
java是跨平台的,需要同一的内存模型来兼容不同不同的操作系统的差异,硬件差异等等。不能因为操作系统硬件的差异导致相同的程序出现不一样的结果
主内存+工作内存概述
线程都有自己的工作内存;每个线程的工作内存之间都是相互屏蔽;
线程操作变量,都是先通过工作内存,然后复制到主内存;其他线程才能再主内存中访问这个变量;
主内存,是可以被所有的线程访问。
主内存+工作内存之间数据交互
1.交互概述
1.线程的工作内存去访问主内存,获取变量值
2.内存之间的基本操作都是原子操作,不可再分割
8大原子操作
1.lock,表示主内存中变量已经被某个线程占有;
2.unlock,表示主内存中变量已经被某个线程释放,其他线程可以去获取这个变量
3.read,表示从主内存中读取变量到工作内存
4.load,表示主内存中读取的值赋值给工作内存中变量
5.use,表示工作内存中的值传递给工作引擎让工作引擎去做操作;
6.assign,表示工作引擎中计算后的值赋值到工作内存中;
7.store,表示工作内存把变量传递到主内存;
8.write,表示工作内存的值存入到主内存中;
交互原则
所有的操作必须符合前后逻辑关系
操作之前必须要满足前后依赖关系
配对操作之前含有其他操作关系,但是必须保证操作前后逻辑关系
变量被加锁多少次,就要被解锁多少次
内存模型三大特点
有序性
理解
线程内部:线程一定会按照串行的方式去执行指令
线程之间:由于cpu的执行权问题,多线程之间执行的任何代码都可能是交叉进行的,除了volatile,synchronized
原子性
主内存和工作内存之间的基本操作都是原子操作
可见性
共享变量被一个线程操作,操作后的记过能被其他线程直到
java可见性实现
volatile
volatile修饰的变量,修改的之后会立即从工作内存同步到主内存之中;实现其他线程对该变量的可见性
synchronized
解锁之前必须把变量的值从工作内存传递到主内存
final
对象的引用是不变的,所以说对所有线程来说都是可见的
java先行发生原则
解决问题
并发情况下,两个操作是否存在冲突的情况;判断数据是否存在并发问题,以及线程是否安全的重要依据
具体原则
1.锁定规则:同一个锁,只有被释放之后才能被另外一个线程再次占用;
2.读写原则:读写是一对操作,下一的读操作必定在写操作之后;
3.对象终结原则:对象被回收之前必须先要被初始化
4.传递性:a操作优先于b操作,b操作优先于c操作,那么a操作也有限与b操作
运行时数据区域
程序计数器
作用
java的多线程是通过计算机内核线程来回相互切换的,java的线程执行到了某一步,cpu执行权被切换到其他线程上时候,这个程序计数器的作用来了, 就是去记录它所属的线程执行到了哪一步,哪一个指令,执行权再次切换回来的时候,这个计数器就会帮助线程准确无误的接着切换之前的代码接着执行。
特点
1.是线程所独有的
2.生命周期和线程周期相同
3.永远都不会有异常
不存在内存异常的情况
本地方法栈
native方法运行的时候用到的内存空间就是本地方法栈
为java语言调用本地方法,也就是调用native修饰的方法服务的。
方法区
特点
方法区被各个线程所共享
存储内容
也称作永久代,存储的都是,经过虚拟机加载之后的字节码文件,类的信息,常量池,静态变量
运行时常量池,存储了编译期的各种字面量(字面量都是常量池的一部分)
栈内存
栈帧
特点
线程所独有,存储的都是临时数据
和线程生命周期一样
3.方法在执行的时候栈内存都会去创建这个方法对应的栈帧,栈栈中存储了这个方法的局部变量表,方法返回值,方法出口等等。我们在调用方法的时候通过方法当中嵌套方法,那么栈内存,同样会为这些方法都去创建对应的栈帧,线程去执行这个栈帧(方法),执行完一个栈帧,这个栈帧对应的内存就会被回收,这就是所谓的弹栈。线程永远都只会在栈内存中最上层的栈帧上执行。并且由于前后调用的方法之间存在着返回值的原因,对应的栈内存中的上下两个栈帧之间也并不是完全割裂的,他们需要返回值的传递。每个栈内存最多可以存储1000-2000个栈帧。所以说。
栈内存大小设置
-Xss128k
给栈内存分配128kb
JVM没有设置总的栈内存大小
操作系统会限制线程的数量,从而达到限制总的栈内存大小
堆内存
堆内存区域划分
新生代
eden
from suvivor
to suvivor
老年代
内存比例
默认的新生代:老年代=1:2
默认eden:from suvivor:to suvivor=8:1:1
比例大小可以通过jvm参数调整
堆内存作用
java存储对象的主要区域
永久代说明
堆内存所说的永久代,只是在jdk1.8版本之前有这个概念,1.8就完全摒弃了这个概念,采用本地硬盘的方式来存储这些数据,有效防止了java这个永久代内存溢出。
jdk1.7
永久代也是属于内存,必须制定大小,大小受限制与所分配的内存大小
jdk1.8
存放在磁盘,可以不指定大小,大小受限制与磁盘
Java对象在内存中存储
分为三部
对象头
Mark Word
记录内容和锁的状态有关
无锁
对象的hashCode值
分代年纪
锁的标记位
是否偏向
偏向锁
偏向线程的id
偏向锁时间戳
分代年纪
锁的标记位
是否偏向
轻量级锁,重量级锁
指向锁的指针
锁的标记位
Gc标记
标记位
指向类的指针
数组长度
实例数据
对齐填充字节
拷贝
深拷贝
基础数据类型拷贝值, 非基础数据类型,新创建对象,并用老对象给新对象字段赋值;
浅拷贝
基础数据类型拷贝值,非基础数据类型拷贝对应的引用
垃圾回收
垃圾收集算法
判断对象是否存活算法
可达性算法
可达性算法过程
GcRoot对象作为起点,向下搜索,搜索走过的路径称为引用链;一个对象到GcRoot没有任何引用链,那么这个对象就是不可达的。
GcRoot对象
new出来的对象
栈内存中栈帧引用的对象
方法区中引用的对象
本地方法区中引用的对象
软引用,弱引用,虚引用
引用计数算法(被废弃了)
概述
给对象添加一个引用计数器,当程序有地方用到这个对象,计数器+1;引用失效就会-1,任何时候如果引用计数器为0,那么就表示该对象要被回收了
问题
循环依赖,造成内存泄露,最后导致内存溢出
垃圾回收算法
分代收集算法
根据新生代,老年代情况的不同,针对新生代,老年代会有不同的垃圾回收算法;
复制算法(新生代)
复制算法(新生代)
eden区域和一块存有对象的survivor的区域中还存活的对象,会复制到另外一块空闲的survivor区域;如果这块survivor区域内存大小不够,那么还会放在老年代当中;
注意:由于老年代中可以存放新生代的对象,如果此时老年代内存也不够,就会触发老年代的fullgc
新生代使用复制算法原因:新生代的对象存活率比较低,复制起来成本低
复制算法示意图
回收前
回收后
标记-整理算法(老年代)
标记-整理算法(老年代)
老年代不适用复制算法原因:老年代的对象存活率高,如果使用复制算法成本太高
根据可达性算法,把存活的对象会向内存区域的一边做迁移跃动,最后会有一个迁移末端;末端之外的对象就是需要被回收的对象;
标记-整理示意图
回收前
回收后
标记-清除算法示意图
标记-清除算法(老年代)
根据可达性算法需要被回收的对象会被标记,然后堆标记的对象的存错回收;
缺点:
1.标记-清除的效率都不高
2.清除会造成很多内存碎片,有可能导致二级gc;
标记-清除算法示意图
回收前
回收后
三色标记算法
概述
三色标记算法是一种垃圾回收的标记算法
作用
让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的
使用范围
JVM中的CMS、G1垃圾回收器 所使用垃圾回收算法即为三色标记法。
三色标记法过程
黑色:代表该对象以及该对象下的属性全部被标记过了。(程序需要用到的对象,不应该被回收)
灰色:对象被标记了,但是该对象下的属性未被完全标记。(需要在该对象中寻找垃圾)
白色:对象未被标记(需要被清除的垃圾)
三色标记存在问题
并发标记的时候,存在漏标的情况
综合概述:老年代垃圾回收算法还是选择标记-整理算法
垃圾收集器
新生代垃圾回收器
serial
单线程回收,用户线程需要停止
PN
多线程回收,用户线程需要停止
PS
多线程回收,用户线程需要停止
以高吞吐量标准设计的:用户线程时间/(用户线程时间+垃圾回收时间)
老年代垃圾回收器
serial-old
老年代的单线程垃圾回收,用户线程需停止
PS-old
老年代多线程回收,用户线程需要停止
以高吞吐量为原则设计:
CMS
多线程回收,以用户线程暂停时间最短为设计标准
回收的时候,用户线程有一段时间不需要停止
CMS垃圾回收器工作流程图
示意图
G1回收器
特点
垃圾回收的时候几乎没有stop the world 时间
新生代,老年代都可以回收
可将内存分成很多歌大小相同的region区域,根据区域之间使用标记-整理算法,区域内部使用标记复制算法;
可预测垃圾回收时间
很重要的一个特点
对region区域做选择性回收,回收价值高的region区域
垃圾回收过程
1.初始标记:停顿所有的用户线程,标记各个region区域中能被gcroot关联到的对象。
2.并发标记:用户线程和gc线程并行,gc线程根据可达性算法找出存活的对象。
3.最终标记:停顿用户线程,并发执行gc线程去标记刚才用户线程操作引用对象的那部分内存。
4.筛选标记:根据可停顿时间,计算出最优的region区域,并发的对最优的区域进行回收(这个时候用户线程和gc线程是可以并发的)。
垃圾回收过程示意图
示意图
分区region
示意图
region特点
将java的堆内存分成2048个大小相同的region块
region大小特点
每个region 区域的大小都是2的N次幂;即1Mb,2Mb,4MB,
region 区域大小在jvm运行期间都是不会被改变
每个region区域的大小都是相等的
存储特点
每一个region区域只会属于Eden, Survivo,old其中的一种
Eden, Survivo,老年代的区域并不是连续;
新增一种新的内存区域,Humongous内存区域,超过0.5个region对象就会被放Humongous区域
三个过程
Young gc
mixed gc
FGc
G1回收器历史
最早出现在jdk7;jdk9以后就是默认的垃圾回收器
G1回收器缺点
G1回收器本身运行垃圾回收程序时相对cms垃圾回收器需要占用更多的系统cpu,内存资源
在内存小于6G时,cms表现优于G1;大于8G,则G1回收器表现更好;6-8G之间,差不多;
相关JVM参数
-XX :G1HeapRegionSize
设定region 区域大小
-XX:+UseG1GC
手动设置G1垃圾回收器;JDK9默认就是G1
-XX:MaxGCPauseMillis
ZGC
ZGC设计目标
停顿时间不超过10ms;
垃圾回收器停顿时间,不会随着活跃对象增加而增加
支持8MB-16TB堆内存
ZGC历史
jdk11引入
JDK15正式发布
ZGC相对以往垃圾回收器特点
已经没有分代收集的概念了
只能运行在64位操作系统上
ZGC内存空间划分
概述
也会像之前的回收器一样,将内存划分成很多个小的块
具体划分
小页面
固定为2MB
用于存放256KB以下的小对象
中页面
固定位32MB
用于存放256KB以上,4MB以下的对象
大页面
大小不固定,但必须是2MB的2次幂的大小
只会存放大于或者等于4MB的大对象,每一个大页面,只会存储一个大对象
示意图
ZGC支持NUMA
内存访问方式
统一内存访问
以前在64位的计算上,内存控制器没有整合进cpu,对内存的访问都必须要通过北桥芯片来控制,任何cpu对内存的访问速度都是一致。
示意图
支持非统一内存访问
cpu都整合到同一个芯片上,各个cpu对于内存的访问会出现争抢,会导致内存访问瓶颈;为了解决这个,将内存和cpu集成在一个单元上,这个就是非统一内存访问
示意图
在NUMA内存访问模式下,cpu访问本地存储器比访问非本地存储器速度快一些;
ZGC支持NUMA,会优先将小页面分配在本地内存,本地内存不够,然后再从远端的内存进行分配。
分配速度很快,收回也很快
中页面,大页面分配不会分配在cpu的本地内存
染色指针?
作用
处理过程
标记阶段
回收阶段
重定位
ZGC垃圾回收
回收策略
小页面优先回收,中页面,大页面尽量不回收
回收过程
回收过程示意图
具体过程
标记阶段
1、多线程并发标记
2、多线程再次并发标记,处理中间并发阶段(用户线程GC线程同时运行)时候,遗漏的对象
转移阶段
1、将原来活跃的对象复制到新的内存空间上,并将原来的内存空间回收;如果发现某一个页全部是垃圾对象,直接全部回收该区域
2、重定位,新的地址值换到原来的对象上面
读屏障
使用场景
当对象的内存地址被转移的时候,刚号在并发阶段应用程序有需要访问这个对象
解释
JVM向应用代码插入一小段代码的技术
相当于有两个操作要一起做的原子操作
过程
1、对已经转移但是还没重定位的对象进行对象的重定位
2、删除对应对象再转发表中的记录的指针的新旧关系
ZGC的GC时机
预热规则
jvm启动预热,如果从未发生过GC,那么就会在堆内存超过10%,20%,30%的时候触发一次GC,来收集GC的数据
基于分配速率的自适应算法
ZGC根据近期对象的分配速度以及GC时间,计算当内存占用达到什么样的阈值的时候出发下一次GC
基于固定时间间隔
流量平稳的时候,自适应需要堆内存占用达到百分之95的时候才会触发
主动触发
和固定时间规则类似。
内存分配不够
内存已经无法再给新的对象分配内存触发
外部触发
代码中直接System.gc()触发
元数据分配触发
元数据区不足时导致触发
ZGC调优
GC触发时机
ZAllocationSpikeTolerance
估算当前的堆内存分配速率,速度估计越快,GC来的越早,速度估算越慢,GC来的越迟
ZCollectionInterval
定 GC 发生的间隔,以秒为单位触发 GC
GC线程
ParallelGCThreads
Stw阶段,GC线程的数量
ConcGCThreads
并发阶段,cpu的数量
Shenandoah
设计目标
将垃圾回收的停顿时间控制在10ms以内
与G1回收器的设计目标一直:低延时为主要目标
历史
jdk12版本以及以后版本
由redhat公司开发
Shenandoah开发使用了很多G1回收器的代码
Shenandoah与G1相同点
基于region的内存布局
标记阶段都是并发标记
Shenandoah与G1不同点
在最终的回收阶段,采用的是并发整理,由于和用户线程并发执行,因此这一过程不会造成STW,这大大缩短了整个垃圾回收过程中系统暂停的时间
默认情况下不使用分代收集,也就是Shenandoah不会专门设计新生代和老年代,因为Shenandoah认为对对象分代的优先级并不高,不是非常有必要实现
采用“连接矩阵”代替记忆集。在G1以及其他经典垃圾回收器中均采用了记忆集来实现跨分区或者跨代引用的问题,每个Region中都维护了一个记忆集,浪费了很多内存,且导致系统负载也更重,「因此在Shenandoah中摒弃了这种实现方式,而是采用连接矩阵来解决跨分区引用的问题」
Epsilon GC
历史
JDK11正式
工作
只负责内存分配,不负责内存回收
使用场景
性能测试
内存压力测试
虚拟机接口测试
极短寿命的工作
极端延迟敏感的应用
参数配置
-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
垃圾回收器搭配使用
serial +old serial
新生代
serial
老年代
old serial
par new + CMS
新生代
par new
老年代
CMS
PS +PO
新生代
PS
老年代
PO
垃圾回收器最佳回收内存大小
serial
几十兆
PS
几个G
CMS
20G
G1
上百G
ZGC
4T
垃圾回收器常用参数
Parallel
XX:+UseSerialGc
XX:+SurvivorRatio
XX:+PreTenureSizeThreshold
XX:+SurvivorRatio
XX:+MaxTenuingThreshold
XX:+SurvivorRatio
XX:+ParallelGCThreads
并行回收垃圾线程数量
-XX:+UseApaptiveSizePolicy
自动选择各区大小比例
CMS
XX:+UseConcMarkSweepGC
使用标记清除算法
-XX:ParallelCMSThreads=n
-XX:CMSInitialingOccupancyFraction
设置GC触发阈值,百分比
-XX:+UseCMSCompactAtFullCollection
再Full GC后,进行一次碎片整理
-XX:CMSFullGCsBeforeCompaction=n
设置几次FullGC后进行一次碎片整理
-XX:+CMSClassUnloadingEnabled
允许对类 元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction=n
当永久区占用达到百分比时,启动CMS回收 (CMS还会根据历史回收情况,判定启动回收不一定会按照百分比阈值)
————————————————
版权声明:本文为CSDN博主「t0mCl0nes」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fly_leopard/article/details/79180783
————————————————
版权声明:本文为CSDN博主「t0mCl0nes」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fly_leopard/article/details/79180783
-XX:+UseCMSinitiatingOccupancyOnly
表示只在达到CMSInitiatingPermOccupancyFraction阈值时,才 进行CMS回收,此时CMS不会根据历史回收情况进行自动回收,只有 达到阈值才会回收
-XX:GCTimeRatio
gc时间占用运行时间比例
-XX:MaxGcPauseMills
停顿时间,是一个建议时间,GC会尝试使用各种手段达到这个时间,比如减小年轻代
G1
-XX:+UseG1GC
-XX:+MaxGCPauseMillis
新生代调整young区域的块的个数达到这个值
新生代回收时间大小
-XX:+MaxGCPauseIntervalMillis
GC间隔时间
-XX:+G1HeapRegionSize
分区大小,size越大,垃圾回收时间越长,GC间隔时间长,也会导致每次GC时间长
1M,2M,4M,8m,16M,32M
-XX:G1NewSizePercent
新生代最小比例,默认5%
-XX:G1MaxNewSizePercent
新生代最大比例,默认60%
-XX:GCTimeRatio
GC时间的建议比例,G1会根据这个值调整整个堆内存大小
-XX:ConcGcThreads
线程数量
-XX:InitiatingHeapOccupancyPercent
启动G1的堆内存占用比例
通用常数
-XX:UseTLAB
使用tlab
-XX:PrintTLAB
打印tlab情况
-XX:TLABSize
设置tlab大小
垃圾回收时间点
线程运行到安全点,安全区域会进行gc信号检查,也就是是否需要做垃圾回收操作;如果需要做,那么线程都会停止在安全点或者安全区域,等待垃圾回收完成;如果不需要垃圾回收,那么就不会停留在安全点,或者安全区域
安全点设置
设置在一些执行时间长的指令上
安全区域设置
相对于安全点来说,就是指令跨度大的安全点;
垃圾回收时间
jdk8及以前版本
当Eden区或者S区不够用了
当老年代空间不够用了
当方法区不够用了
子主题
jdk9开始
根据垃圾回收器的不同,会有所不同
System.gc()(通知jvm进行一次垃圾回收,具体执行还要看JVM,另外在代码中尽量不要用,毕竟GC一次还是很消耗资源的)
GC日志分析
jvm性能调优
调优常见问题
1、jdk1.7,1.8,1.9默认的垃圾回收器是什么?
jdk1.7 默认垃圾收集器PS(新生代)+PO(老年代)
jdk1.8 默认垃圾收集器PS(新生代)+PO(老年代)
G1回收器
2、常见的HotSpot垃圾回收器组合有哪些?
serial +old serial
新生代
serial
老年代
old serial
PN + CMS
新生代
par new
老年代
CMS
PS +PO
新生代
PS
老年代
PO
3、所谓调优,到底是在调什么?
1、提高用户线程吞吐量
2、提高用户线程相应时间
4、PN +CMS 怎样才能让系统基本不产生FGC
扩大JVM内存
调整内存比例
加大新生代区间比例
提高去Survivor区域比例
提高新生代到老年代的年纪
避免代码内存泄露
5、PS +PO 怎样才能让系统基本不产生FGC
避免代码内存泄露
6、G1回收器是否分代?G1回收器会产生FGC吗?
不分代收集,在G1回收器概念里面,已经没有新生代,老年代的概念,所有的堆内存区域划分为不同的区域
会发生FGC
7、如果G1回收器发生FGC?
1、扩大内存
2、提高CPU性能(可以提高回收效率)
3、降低MixedGC 触发的阈值,让MixedGc提早发生
8、生产环境可以随随便便dump吗?
小的堆内存影响不大;大的堆内存会有服务器卡顿
9、常见OOM问题有哪些?
栈,堆,方法区直接内存溢出
jvm调优实战
线程
案例1、
CPU经常飙升100%,如何调优
解决步骤
找出哪个进程的CPU高
top 命令
找出该进程中哪个线程cpu高
top -hp命令
导出该线程的堆栈信息
jstack
线程线程占比和垃圾回收线程的占比对比
案例2、
假如100个线程很多线程都在等待,找到持有这把锁的线程
1、找到java的线程,看有哪些线程正在运行,然后拷贝出来运行的线程的唯一标记
2、再去等待线程中,找这些线程等到的是哪个线程,拿出这些线程id和运行的线程对比,,可以找到持有锁的线程;
内存
案例1、
案例1
1、为啥原网站很慢
文档加载斤内存中行程的java对象导致内存不足,频繁GC,导致stw时间过长
2、内存加大之后更卡顿
内存大,然后FGC时间过长,导致用户线程暂停
从PS垃圾回收器,设置成PN+CMS 或者G1回收器
子主题
案例2、
内存消耗不超过10%,频繁发生FGC?
子主题
案例3、
线程池使用不当,导致OOM
JVM调优的目的
JVM调优的目的
1、吞吐量
2、响应时间
1、减少垃圾回收时候的stop the world的时间
调优前需要明确是以高吞吐量为要求,还是以响应时间低为要求;还是说满足一定的相应时间的情况下,达到多少的吞吐量
垃圾回收器选择
吞吐量优先
PS+PO回收期
场景
数据计算
数据挖掘
追求响应时间
G1回收器
场景
网站
API
jvm编译
编译过程
词法解析
token流
语法解析
抽象语法树
语义解析
经过注解的抽象语法树
字节码生成器
字节码文件
指令重排
原因
编译器对代码进行空间复杂度时间复杂度的优化,导致了代码实际的执行顺序和书写的不一样;
现象
程序先执行下面的代码,后执行上面的代码
指令重排原则
重排序的代码没有先后逻辑关系
重排序不影响结果
类加载机制
字节码增强技术
概述
作用
对java的字节码进行修改,增强功能
操作
修改二进制的class文件
目的
减少冗余代码,提高性能,加密代码
实现方式
AspectJ
概述
修改java字节码工具类
作用时间:编译期
Javassist
概述
修改java字节码的工具库
功能
可以直接生成一个类
在已经编译的类里面添加新方法,修改方法
修改时间:运行期
ASM
概述
ASM是一个java字节码操作框架
功能
直接生成class文件
拦截java文件被类加载器加载之前修改类
修改时间:编译期
APT
双亲委派机制
类加载器种类
BootstrapClassLoader
加载java核心库 java.*构建ExtClassLoade,AppClassLoader
ExtClassLoader
加载扩展库,如classpath中的jre ,javax.*
AppClassLoader
加载项目代码所在目录的class文件
自定义加载器
用户自定义的类加载器,可加载指定路径的class文件
加载器的区别
双亲委派机制作用
1.防止类被多个类加载器加载
2.保证核心的class文件不被篡改
双亲委派机制
类加载器需要加载类的时候,会先去让他的父类去加载;父类如果没有加载这个类,也没有权限加载,那么就会子类加载;如果有加载权限,那么就会接着让他的父类再做次判断;
双亲委派机制流程
三个重要判断
当前类是否已经被加载
当前类加载器是否还有父类加载器
当前类加载器是否有权限加载这个类
spi机制
spi
JAVA内置一种服务发现机制
使用场景
根据实际需求替换,扩展框架源码的实现策略
spi实现过程
调用ServiceLoad.load方法,传入接口字节码对象
根据字节码类型到meta-info包下面找到对应的配置文件
读取配置文件中的类全路径
获得类的全路径,通过反射获取到对象
把对象存储到linkedHashMap中
spi优缺点
优点
能够让接口更方便找到拓展的实现类
缺点
不能单独获取这一个实现类,获取了接口的所有实现类,造成了性能消耗
spi配置过程
实现某个想要实现的接口
resource包下配置相关的包路径和文件名
文件中配置类的全路径
spi机制破坏了双亲委派机制
spi机制创建对象的类加载器是classLoad,和传统的类加载器不存在父子关系
对象创建过程
1.将对象的字节码文件加载进内存
2.栈内存给对象分配一个引用变量
3.堆内存开辟一块内存空间
4.给堆内存对象属性做默认初始化赋值
5.给对象属性做显示初始化
6.对代码块进行初始化
1.父类构造代码块
2.当前类的构造代码块
3.父类的构造方法
4.当前类的构造方法
7.将堆内存的地址赋值给栈内存的引用
编译器
jit热点编译
将频繁调用的代码直接加载进内存,无需从硬盘上去加载这些文件
主流编译器
sun公司hotspot
从jdk10之后,JIT有三款
C1
客户端编译器
C2
服务端编译器
Graal 编译器
这三款编译器都是热点编译器,jdk17已经删除了Graal 编译器
还有其他公司开发的,也就是其他有自己开发的openjdk
0 条评论
下一页