文章目录
接上篇的垃圾收集器~
一、G1(Garbage First)
2012 JDK 7u4官方支持
2017 JDK9默认
- 同时注重吞吐量、低延迟,默认的暂停目标是200ms(追求低延迟)
- 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是标记+整理算法,两个区域之间是复制算法
1. G1垃圾回收阶段概述
- 新生代垃圾收集
- 新生代垃圾收集+并发标记(CM)
- 混合收集
不断循环 当老年代内存超过一定阈值,会进行并发标记;进入混合收集,就是对新生代和老年代都进行一次规模较大的垃圾收集。
2. Young Collection 新生代垃圾回收
堆被划分为多个大小相等的Region,每个区域都可以独立作为Eden、幸存区、老年代。
类加载时创建的对象会分配到Eden区中,当区域逐渐占满,会触发一次新生代垃圾回收,STW。
以拷贝算法放入幸存区:
再工作一段时间,当幸存区的对象也比较多了,对象存活年龄超过一定时间,,又会触发新生代垃圾回收,幸存区一部分对象会晋升到老年代,不够年龄的会拷贝到另一个幸存区:
3. Young Collection+ CM
CM:并发标记
初始标记是标记根对象
- Young GC 会进行GC Root的初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会STM),由一下JVM参数决定
-XX:InitiatingHeapOccupancyPercent=percent
(默认45%)
4. Mixed Collection
会对E,S,O进行全面垃圾回收。
会挑出老年代中回收价值最高的几个区域,这样复制的区域少了,需要的gc时间就少了。(优先收集垃圾最多的区域)
最终标记(Remark)会 STW
拷贝存活(Evacuation)会 STW
和CMS类似,并发失败了以后才算full GC。当收集速度比不上垃圾产生速度时,退化为串行GC(才会叫做full gc)
5. 跨代引用
老年代引用新生代的问题:
将老年代的区域再进行细分(CardTable),一个card引用了新生代,那么标记为脏card。这样gc的时候找gc root不用遍历整个老年代,只要关注脏card即可。 减少搜索范围,提高效率。
以下示意图:粉色部分为脏卡,记录在Remembered Set(新生代)内;在脏卡区遍历gc root
标记脏卡:在每次对象引用变更时,都要更新卡表中的卡。这不是立刻更新的,而是将更新指令放在一个队列中,将来用一个线程去执行
在引用变更时通过 post-write barrier + dirty card queue
concurrent refinement threads 更新 Remembered Set
6. remark (重新标记阶段)
下面这张图是并发标记阶段,对象的处理状态。
黑色是已经处理完成的,结束时会被保留的对象
灰色是正在处理中的
白色是尚未处理的
最后全部处理完毕以后,没有被引用的会被当成垃圾。
有写屏障 pre-write barrier 技术,在对象引用改变前加入一个队列(satb_mark_queue) 最后remark阶段配合这个队列进行进一步判断。
7. JDK 8u20 字符串去重
- 优点:节省大量内存
- 缺点: 略微多占用了CPU时间,新生代回收时间略微增加
-XX:+UseStringDeduplication
开启这个选项可以进行字符串去重
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
//让这两个字符串对象都引用同一个char[]数组
将所有新分配的字符串放入一个队列
当新生代回收时,G1并发检查是否有字符串重复
如果它们值一样,让它们引用同一个 char[]
注意,与 String.intern() 不一样
String.intern() 关注的是字符串对象,而字符串去重关注的是 char[]
在 JVM 内部,使用了不同的字符串表
8. JDK 8u40 并发标记类卸载
对垃圾回收器的功能增强
可以把类都卸载掉!!
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
-XX:+ClassUnloadingWithConcurrentMark
默认启用
9. JDK 8u60 回收巨型对象
- 一个对象大于 region 的一半时,称之为巨型对象
- G1 不会对巨型对象进行拷贝(毕竟效率低 不会标记-复制,只是计算引用)
- 回收时被优先考虑
- G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉
10. JDK9并发标记起始时间调整
JDK9之前设置参数,老年代占堆内存的大小达到45%阈值后,并发垃圾回收开始。
JDK9之后可以动态调整阈值,这个参数-XX:InitiatingHeapOccupancyPercent
用来设置初始值,在之后过程中会数据采样并动态调整,总会添加一个安全的空档空间,让堆的空间足够大,能够容纳浮动垃圾。