判断垃圾对象
- 引用计数法:给对象添加引用计数器,每当有一个地方引用它,计数器+1,当引用失效,计数器-1,任何时候计数器为0,都表示该对象为可回收对象。但是主流虚拟机并没有选择该算法来管理内存。最主要的原因是因为它很难解决对象之间相互循环引用的一个问题。
可达性分析算法:将
GC Roots对象作为起点,从这些点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的为垃圾对象。Mark-Sweep(标记清除)
- 容易产生碎片
- Copying(复制算法)
- 不会产生碎片,但会浪费空间,需要移动存活的对象,较多的空间无法被利用
- Mark-Compact(标记整理)
- -XX:UseSerialgc : 新生代使用serial,老年代使用Serial Old GC 串行
ParNew + CMS
- CMS:(并发标记清除算法 )
Parallel Scavenge + Parallel Old
- -XX:UseParallelGC :新生代使用Parallel ScavengeGC,老年代使用Parallel Old(标记整理)
新生代进入老年代机制
- 长期存活对象将进入老年代:既然虚拟机采用了分代收集的思想来管理内存,那么内存回首时就必须能识别那些对象放在新生代,那些对象放到老年代,虚拟机给每个对象一个对象年龄计数器。对象在Eden出生,第一次经历minor GC后存活,并且能够被Survivor容纳的话,将被移动到Survivor区域,并将对象年龄设置为1,对象在Survivor区域中每熬过MinorGC,年龄就会+1,当年龄增加到15时,会晋级到老年代。CMS(默认为6次),可以通过参数
-XX:MaxTenuringThreshold来设置 - 对象动态年龄判断:当前对象的Survivor区域里,一批对象的总大小大于这块Survivor区域内存大小的50%(
-XX:TargetSuvivorRatio指定),那么此时大于等于这批对象年龄最大值的对象就可以直接进入老年代了,例如Survivor区域里有一批对象,年龄1+年龄2+年龄3对象超过Survivor区域的50%,此时就会把年龄3及以上的对象都放如老年代,这个规则其实时希望可能长期存活的对象尽早进入老年代。动态年龄判断机制一般是在minor gc之后进行触发。 - 老年代空间分配担保机制:年轻代每次minor gc之前jvm都会计算下老年代的剩余可用时间,如果这个可用空间小于新生代现有的所有对象大小之和,就会开启担保机制,计算前几次新生代进入老年代的平均大小,如果该值<老年代可用空间大小,就会进行minorGC,如果>老年代可用空间大小,就会进行full GC。
-
ZGC标记过程
初始化标记stw
- GCroots 扫描并标记根对象。
- 并发标记
- 三色标记算法:
- 黑色:标记根对象,表示该对象与子对象都已经被扫描过。
- 白色:未被扫描过
- 灰色:根对象被扫描过但子对象没有被扫描
- 漏标问题:
- GC线程1完成所有的标记,线程2还处于半完成状态
- 引用发生以下改变,c=C(读引用)[线程1完成,线程2没完成,在线程2标记过程中在线程1刚刚完成的对象增加新的引用],c=null。(业务线程继续)
- 线程1和线程2完成所有标记,C对象是白色,被漏标,被错误的回收。
- 三色标记算法:
- 再标记阶段stw
- ZGC读屏障,在并发标记的过程中执行的代码会被标记成读引用,进行记录,在重新标记阶段专门来解决漏标问题。
- G1采用的是写屏障
- 并发转移准备
- 分区中的筛选,进行选择性回收。
- 初始转移stw
- 转移GC Roots对象
- 并发转移
- 转发表(Forwoard table),记录老得和新的地址
- 读屏障
- 对象重定位(下次gc)
- 对上次标记为绿色的指针进行修正(通过转发表)
为什么需要3中颜色:
- 其中1中颜色用于区分上次未修正的引用。把上一次未修正的指针,放到下一个并发标记阶段,修正完后,并删除转发标中的地址。
CMS的缺点
- 对处理器敏感
- 浮动垃圾:在并发清理阶段,用户线程仍然在制造垃圾,只能留给下一次GC回收
- 并发失败:如果失败会触发Seraial Old,执行Full GC,反而导致停顿的时间更长
- 内存碎片:cms采用标记清除算法,可以通过手动配置进行碎片整理
