Garbage First垃圾回收器 G1 jdk9取代了CMS垃圾回收器
在jdk中进程 2009 JDK 6u14体验
2012 JDK 7u4 官方支持
2017 JDK 9 默认
适用场景
同时注重吞吐量和低延迟,默认的暂停目标是200ms
超大堆内存,会将堆划分为多个大小相等的Region
整体上是标记+整理算法,两个区域之间是复制算法
相关参数
-XX:+UseG1GC 开关
-XX:G1HeapRegionSize=size G1堆中的Region大小
- -XX:MaxGCPauseMills=time
阶段
young Collection: 新生代垃圾回收
Young Collection+Concurrent Mark: 新生代垃圾回收+并发标记
Mixed Collection: 混合收集
先新生代垃圾收集
当老年代内存超过阈值时,下一次新生代垃圾回收会开启并发标记,并进行混合收集,
对新生代,老年代区域垃圾回收
工作流程
young Collection: 会STW,停止其他用户线程
先伊甸园垃圾回收,伊甸园满了后会进行新生代垃圾回收,复制幸存对象到幸存区to
交换幸存区from和幸存区to位置,当幸存区内快存满了,再次垃圾回收
一定时间后,一些年龄达到的就晋升老年代
新生代垃圾回收的跨代引用
新生代垃圾回收是找根对象,可达性分析,找存活对象,复制算法到幸存区
但是,找新生代对象的根对象时,根对象一部分会在老年代中,
如果直接遍历老年代对象是效率很低
将老年代划分为无数个card,每个card空间512k,
如果card中有对象引用新生代对象,会对这个card标记为脏card
新生代对象空间会记录引用它的脏card地址,减少标记GCRoot的时间
然后minorgc时,新生代清理复制算法,引用变更的时候,
通过post-writer barrier + dirty carf queue队列
用线程concurrentRefinementThreads更新RememberedSet
Young Collection + Concurrent Mark:
新生代垃圾收集时对根对象GCRoot初始标记
老年代占用堆空间比例达到阈值时,进行并发标记(不会停止用户线程STW)
阈值: -XX:InitiatingHeapOccupancyPercent=percent)(默认45%)
拷贝存活 会STW暂停用户线程
会对伊甸园,幸存区,老年代都进行一个全面的垃圾回收
新生代: 伊甸园区进行复制算法到幸存区,到年龄就晋升老年代
老年代: 采用复制算法将幸存对象复制到老年代另一个区域
G1垃圾回收器会根据最大暂停时间有选择的回收
(只对可能垃圾较多的区域回收,减少暂停时间达到目标)
Mixed Collection:
进行最终标记(防止并发下的引用改变) 会STW暂停用户线程
拷贝存活 (如上) 会STW
重新标记remake
在G1和CMS中都有应用,就是在并发标记后都要重新标记
因为如果在标记的同时,用户线程将一个对象的引用取消了,该对象被标记清除,
而此时,该对象又被用户线程引用了,就会出错
当对象引用发生改变时,就会将其加入一个写屏障,(就是将对象加入队列中,并标记为未处理)
并在重写标记阶段将对象从队列中取出,重新检查,如果是被引用就不清理
G1的jdk8后的一些优化:
jdk8u20 : 字符串去重
将所有新分配的字符放入一个队列,当新生代回收时,G1并发检查是否有字符串重复
如果值一样,就引用同一个char[]
在jvm内部,使用了不同的字符串表
和String.intern()不同,String.intern关注的是字符串对象
而字符串去重关注的是char[],
在jvm内部,使用了不同字符串表
优点: 节约内存
缺点: 略微占用cpu,新生代回收时间增加
jdk8u40: 并发标记类卸载
所有对象都经过并发标记后,就能指定哪些类不再被使用,
当类加载器所有类不再使用,就卸载所有类
-XX:+ClassUnloadingWithConcurrentMark
jdk8u60: 巨型对象回收
一个对象占region一半时,叫巨型对象
G1不会对巨型对象进行拷贝
回收时被优先考虑
G1跟踪老年代所有的incoming引用,
老年代incoming引用为0的巨型对象可以在新生代垃圾回收时被处理
jdk9: 并发标记起始时间调整
并发标记在堆空间占满前完成,否则退化为FullGC
jdk9前,要使用-XX:InitiatingOccupancyPercent(默认45%)
jdk9可以动态调整
-XX:InitiatingHeapOccupancyPercent 设置初始值
进行数据采样并动态调整
添加一个安全的空档空间