Java 8 内存管理原理解析及内存故障排查实践( 六 )


G1同样有年轻代和老年代的概念,只不过物理空间划分已经不存在,逻辑分区还存在,G1会把堆切成若干份,每一份当作一个目标,在部分上目标很容易达成,G1在进行垃圾回收的时候,将会根据最大停顿时间设置值动态选取部分小堆区垃圾回收 。

Java 8 内存管理原理解析及内存故障排查实践

文章插图
G1的特点是尽量追求吞吐量,追求响应时间,并发收集,压缩空闲空间不会延长GC暂停时间,更容易预测GC暂停时间,能充分利用CPU、多核环境下的硬件优势 , 使用多个CPU对STW进行控制(200ms以内)灵活的分区回收,优先回收花费时间少的或者垃圾比例高的region新老比例也是动态调整,不需要配置;年龄晋升也是15,但是可以动态年龄,当幸存者region超过了50时 , 会把年龄最大的放入老年代 。
G1动态Y区域设置,G1每个分区都可能是年轻代或者老年代,但是同一时刻只属于一个代,分代概念还存在,逻辑上分代方便复用以前分代逻辑 , 在物理上不需要连续,这样能带来额外好处,有的分区内垃圾比较多,有的分区比较少,G1会优先回收垃圾比较多的分区,这样可以花费少量的时间来回收这些分区垃圾,即收集最多垃圾分区;但是新生代回收不适合这种,新生代达到阈值时发生YGC , 对整个新生代进行回收或者晋升幸存,新生代也分区是方便动态调整分区大?。?在进行垃圾回收时,会将存活对象拷贝到另一个可用分区上,这样也能避免一定程度的内存碎片化过程,每个分区的大小都是在1M- 32M之间,取决2的幂次方 。
Humingous:如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象 。这些巨型对象 , 默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响;为了解决这个问题,G1划分了一个Humongous区 , 它用来专门存放巨型对象 。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储 。为了能找到连续的H区,有时候不得不启动Full GC 。
CardTable:记录每一块card内存区域是否dirty,如果在发生YGC时,怎么知道那些是存活对象,并且其它代区域有没有引用这部分对象,于是把内存划分了很多card区域, 每个区域大小不超过512b,当该card区域里的对象有引用关系,将当前card置为“dirty”,并且使用卡表(CardTable)来记录每一块card是否dirty,在进行GC时,不用遍历所有的空间, 只需要遍历卡表中为"dirty"或者说布尔符合条件的card区域进行回扫 。
Java 8 内存管理原理解析及内存故障排查实践

文章插图
CSet:Collection SET用于记录可被回收分区的集合组 ,  G1使用不同算法,动态的计算出那些分区是需要被回收的 , 将其放到CSet中,在CSet当中存活的数据都会在GC过程中拷贝到另一个可用分区,CSet可以是所有类型分区,它需要额外占用内存,堆空间的1% 。
RSet:RememberedSet 每个Region都有一个Rset,是一个记录了其他Region中的对象到本身Region的引用 , 它可以使得垃圾收集器不需要扫描整个堆去找到谁的引用了当前分区对象,是G1高效回收的关键点,也是三色算法的一个以来点 。
Java 8 内存管理原理解析及内存故障排查实践

文章插图
RSet和卡表的区别是什么?
卡表记录的是堆内存中card有没有变成"dirty",但是它本身不知道dirty里面哪些是引用了的对象,它是一个大维度的一个记录 , RSet是记录自身Region中对象引用了其它Region中的那些对象,详细的记录对方引用对象信息 , G1使用了两者的结合,实现了增量式的垃圾回收 , 并优化跨区引用的最终处理 。
SATB算法:是一种基于快照的算法,它可以避免在垃圾回收时出现对象漏标或者重复标记的问题 , 从而提高垃圾回收的准确性和效率 , 在垃圾回收开始时 , 对堆中的对象引用进行快照,然后在并发标记阶段中记录下所有被修改过对象引用 , 保存到satb_mark_queue中,最后在重新标记阶段重新扫描这些对象,标记所有被修改的对象,保证了准确性和效率 。
SATB算法在remark阶段不需要暂停遍历整个堆对象 , 只需要扫描“satb_mark_queue”队列中的记录,避免了这个阶段长耗时 , 而cms的增量算法在这个阶段是需要重新扫描GC Roots标记整个堆对象,导致了不可控时间暂停,总的来说G1是通过回收领域应用并行化策略 , 将原来的几块大内存块回收问题,演变成了N个小内存块回收,使得回收效率可以高度并行化,停顿时间可控,可以与用户线程并发执行,将一块内存分而治之 。


推荐阅读