深入理解jvm
1.类加载机制
通过javac编译成class文件 通过类加载器 加载到jvm虚拟机,方法区里面放二进制数据,堆区里面class对象
加载步骤:
加载->验证->准备->解析->初始化
类加载器:
根加载器->扩展类加载器->应用加载器加载->自定义加载器
一般采用的双亲委派机制,先判断自己有没有加载过这个class,然后让父加载器加载,一直向上委托,直到委托到根加载器,然后再判断自己能否加载;如果不能就向下委托,让子加载器加载,直到class被加载;这样可以避免重复加载,热部署就是跳过双亲委派机制,重复加载类。
2.内存模型
-
堆
new的对象就放在堆,jvm内存调优也是优化这个内存
- 新生代 eden区 survivor1 survivor2
- 老年代
-
栈
在线程执行方法的时候,会栈开辟一个栈帧,一般用于存放方法的局部变量之类的,栈帧遵循先进后出
-
本地方法栈
存放一个c++写的本地方法
-
程序技术器
来记录线程执行代码到那一行,在线程上下文切换的时候就知道从那一行代码来执行
-
方法区
用来存放常量、静态变量和类信息
2.1堆
当eden区被放满,就会发生样gc,然后把未回收的对象放在survivor1区;当eden区再次被放满,再次发生养gc,把eden区和survivor1区的垃圾对象回收,剩下的对象放在survivor2区,对象分代年龄达到一次次数,会被放入老年代;如果老年代满了,会发生负gc,所以线程会被停止,等待垃圾回收完成。
在样gc之前,会判断年轻代所有的对象大小是否大于老年代剩余空间,同时没有开启担保配置,那么会直接负gc,如果开启了担保配置,那么还会判断判断老年代剩余空间是否小于年轻代每次样gc后放入老年代对象的平均大小,如果小于会直接发生负gc;之后在做样gc效率会高很多,也就是说jvm判断样gc后大概率还要负gc,那么先负gc再样gc效率更高
-
大对象到达到一定大小直接进入老年代
-
分代年龄达到一定数量会进入老年代
-
如果有一批对象放在survivor区,总量大小超过survivor区的百分之50,会被直接放到老年代
3.垃圾收集器
3.1gc垃圾回收算法
如何判断是否回收对象
-
引用计数法
对象每引用一次就会+1,释放了就-1,计数值为0就代表是垃圾对象可以回收;但是如果a引用b,b引用a,就会导致他的值永远为1
-
可达性分析(目前jvm使用这种方式)
通过gc root的引用链路来判断,在这个链路上的都是非垃圾对象
gc算法:
标记清除,先扫描,把需要删除的标记上,扫描完后,清除, 会产生空间碎片
标记整理,删除后会移动,不会产生碎片,
复制删除,有两个空间,把不需要删除的对象移到第二个空间,然后直接把第一个空间的删除
3.2垃圾收集器种类
串行(年轻代-复制删除/老年代-标记整理):
就是在负gc的时候单线程执行,速度慢,用户线程等待时间长
并行(年轻代-复制删除/老年代-标记整理):
负gc的时候多线程执行。
prenew(年轻代-复制删除):
也是多线程执行,主要配合cms使用,prenew负责年轻代,cms负责老年代
cms(老年代-标记清除 也可以通过参数进行整理空间):
初始标记:单线程或者多线程,标记初始的gc root 这个过程很短,会中止用户线程
并发标记:多线程进行并发标记,通过gc root引用链路往下找,不会中止用户线程,这个过程最耗时间,会存在期间用户线程产生新的垃圾对象,这种只能等下次负gc清理
重新标记:重新标记一遍,主要处理漏标的问题,会中止用户线程,但是处理时间远远小于并发标记
并发清理:清理未标记的对象,不会中止用户线程,期间产生的用户线程会标记成黑色不做处理。
重置标记:清理重置标记记录
cms不是在老年代内存到达百分之百才触发负gc,而是默认达到百分之92触发,需要预留一些空间,防止并发失败,从而切换成串行负gc
G1:
初始标记:中止业务线程,记录gc root直接能引用的对象。
并发标记:不会中止业务线程,从过cg root的对象引用链路往下找,这个过程最耗时,但不影响业务。
重新标记:中止业务线程,主要处理漏标的问题。
筛选回收:会根据配置的停顿时间来清理,也就是如果配置了200m,那么会中止业务线程200m,剩下没有回收的等下次;还会根据清除时间对区域排序清除。
将堆内存分为很多小区域,然后老年代年轻代都是采用的标记复制法,还多了一个humongous区专门放大对象,然后负gc会把年轻代和老年代humongous区全部收集回收。
ZGC:
初始标记:中止业务线程,记录gc root直接能引用的对象。
并发标记:不会中止业务线程,从过cg root的对象引用链路往下找,这个过程最耗时,但不影响业务。
重新标记:中止业务线程,主要处理漏标的问题。
并发转移准备:根据清除时间对区域排序
初始移动:把会gcroot 直接引用的对象 且标记的对象,先用标记复制法移动,这个过程会stw。
并发移动:通过gc root的引用链路把其他标记的对象移动过来,这个过程不会stw。
zgc把内存分为小中大三种分页,大对象一般放入中页或者大页,一般都是会小页进行gc,zgc只会在初始标记、重新标记、初始移动的时候stw,大大减少了stw的时间,一般stw时间只需要1m。