Garbage First
2004论文发布 ----> 2009 JDK 6u14 体验 ----> 2012 JDK 7u4 官方支持 ----> 2017 JDK9 默认
适用场景:
- 同时注重吞吐量和低延时,默认的暂停目标200ms
- 超大堆内存,会将堆划分为多个大小相等Region
- 整体上是标记整理算法,两个区域之间是复制算法
相关JVM参数:
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
G1 垃圾回收阶段
G1三个循环回收阶段
1、Young Collection (新生代)
会STW
1)、将内存换分为多个区域,E代表Eden
2)、当Eden区域逐渐被占满,触发 Minor GC,采用复制算法,将幸存的对象复制到幸存区。S:代表幸存区。
3)、在经过一段时间之后,幸存区逐渐被占满,那么将年龄够的对象晋升到老年代。将不符合年龄要求的对象,拷贝到新的幸存区。
2、Young Collection + CM
在Young GC时会进行GC Root的初始标记(找到根对象,STW时进行,在上一个阶段执行)
老年代占用堆空间比例达到阈值时,会进行并发标记(根据根对象的引用链,找到其他对象,不会STW),由下面的JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent ( 默认45% )
3、Mixed Collection
会对 E、S、O 进行全面的垃圾回收
最终标记(Remark)会STW,因为并发标记可能有遗漏
拷贝存活(Evacuation)会STW
-XX:MaxGCPauseMillis=ms (最大暂停时间)
对于新生代,将Eden区的幸存对象拷贝到幸存区,将其他幸存区里面的对象,该晋升的晋升,该继续保留的保留。其他的就全部进行回收了。
对于老年代,因为老年代会很大,默认45%,那么根据我们最大暂停时间,可能在这设置的时间内,我们不能讲全部老年区进行判断回收,那么系统就会判断,那么回收等级高,就优先回收哪些优先等级高的。
Full GC
CMS,在碎片少的时候,是并发执行垃圾回收的,如果要是并发失败,会退化为 Serial GC,那么这种产生的GC 才叫Full GC
G1 ,当你垃圾产生的速度 小于 垃圾回收的速度,这不叫Full GC。 当你垃圾产生的速度 大于 垃圾回收的速度,才会产生 Full GC,并发回收失败,进行并行回收。
Young Collection 跨代引用问题
新生代回收的跨代引用(老年代引用新生代)问题
当新生代对象被老年代对象引用,我们将老年代再进行细分,每个大约512k大小。使用卡表技术
如果这个老年代引用了新生代对象,那么这个卡,我们就标记为脏卡。以后我们就不用遍历整个老年代,减少搜索范围,提高GC Root的寻找效率。
新生代会记录脏卡,那么在 新生代垃圾回收时,就会根据Eden记录脏卡区域进行遍历查询GC Root
Remark 重新标记
在并发标记过程中,产生了新的引用,原本应该被回收的对象被引用了。但是并发标记并没有对 被引用的对象进行重新标记。
所以重新标记就是为了解决这种情况。
具体做法,在并发标记时:当对象引用 发生改变时,JVM就会对该对象加入一个 写屏障 (只要对象引用发生了改变,就会触发写屏障)。
写屏障:会将该对象加入一个队列(satb_mark_queue队列)之中,将该对象变为正在处理中的状态,进入重新标记阶段。
重新标记,会STW,将队列中的 对象取出来,进行再一次的判断出来,判断队列中的对象是回收还是保留。
G1 垃圾回收器的优化(8->9的优化)
JDK 8u20 字符串去重
优点:节省大量内存
去电:略微多占用cpu时间,新生代回收时间略微增加
-XX:+UserStringDeduplication
jdk8 中字符串的存储底层实际采用的char数组进行存储,new两个,那么也是存两个char数组
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
- 将所有新分配的字符串放入一个队列
- 当新生代回收时,G1并发检查是否有字符串重复
- 如果它们值相同,让它们引用同一个char[] // 例如上面代码,s1,s2 引用同一个
- 主义,与String.intern() 不一样
- String.intern() 关注的是字符串对象 // 用的StringTable去重,是用的常量池,这个针对的是对象
- 而字符串去重关注的是char[]
- 而JVM内部,使用了不同的字符串表
JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再被使用,则卸载它所加载过的所有类。
例如,很多框架都是用自定义加载器。
-XX:+ClassUnloadWithConcurrentMark // 默认启用
JDK 8u60 回收巨型对象
- 一个对象大于region的一半时,称之为巨型对象(你也可以占多个region)
- G1不会对巨型对象进行拷贝
- 回收时,巨型对象优先考虑
- G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉。
JDK 9 并发标记起始时间的调整
- 并发标记必须在堆空间占满前完成,否则退化为FullGC
- JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
- JDK 9 可以动态调整
- -XX:InitiatingHeapOccupancyPercent 用来设置初始值,默认45%
- 进行数据采用并动态调整 — > 阈值
- 总会添加一个安全的空档空间
这样可以尽可能避免 G1 GC 退化为 FullGC