传闻你醒目JVM?那你相识过OpenJ9中的BalancedGC吗?
听说你精通JVM?那你了解过OpenJ9中的BalancedGC吗?
扩展阅读OpenJ9中的Balanced GC介绍
OpenJ9提供一款非常类似于G1的垃圾回收器,称为Balanced GC。
Balanced GC也是一款基于分区的垃圾回收器,而且也是分代的垃圾回收器。
在新生代的垃圾回收中采用复制算法,对于老生代也是采用增量的并发标记。也有类似G1中混合回收的概念(在回收新生代的,回收垃圾比较多的分区),也有全局的Full GC。
虽然Balanced GC在很多地方和G1 GC非常相似,两者在很多地方还是有所不同的,例如在内存分配、垃圾回收等细节之处,两者在设计、实现方面都有所区别。本节着重介绍两者的不同,供读者扩展视野,如表6-1所示。
内存管理的区别
G1堆空间划分为新生代空间和老生代空间。而Balanced GC默认是25个代,分别是新生代空间、过渡代空间、老生代空间,其中第0代是新生代空间,第24代是老生代空间,第1代到第23代是过渡代空间,堆空间示意图如图6-26所示。
大对象设计的区别
G1中的大对象是指超过一定大小的对象。当对象超过一个分区的大小,需要多个分区时,会寻找一块连续的内存。
而Balanced GC对于大对象处理稍有不同。大对象通常来自数组对象(实际上几乎没有一个非数组对象的大小达到512KB,而数组对象则非常容易达到512KB),所以OpenJ9专门对数组进行了优化。对于数组的存储方式称为Arraylet,一个Arraylet通常包含一个spine(主干)和leaves(叶子),其中spine主要包含数组对象的组织情况,leaves存储的是数组对象的数据。一个Arraylet在内存中的布局如图6-27所示。
图6-27描述的是Arraylet一般的存储情况。实际上,Arraylet在内存中的布局与数组对象的大小相关,可以简单如下
1)在一个分区中连续存储,通常是一个数组对象的大小小于分区的大小。
2)在多个leaves分区不连续存储数据,但spine中并不包含数据。
3)混合存储,spine中包含Arraylet的组织信息和部分数据,多个leaves仅仅包含数据。图6-27就是一个混合存储的例子。
OpenJ9引入的Arraylet机制有效解决了大数组对象在分区内存管理下要求内存连续的存储问题。但遗憾的是该方案并不完善,在一些情况下可能导致应用运行出错。最典型的情况就是应用使用JNI的API,例如JNI中有一些API(如GetPrimitiveArrayCritical)可以获得对象的地址,并在本地代码中使用对象的地址进行读、写操作,由于使用地址访问内存,而底层数据在内存中并不连续,这样的方式会导致应用崩溃。所以在OpenJ9中,对于分区的垃圾回收器,在使用这类API必须特殊处理,一个典型的方法就是把Java对象的内存复制到本地内存中,当使用完毕后再将本地内存数据复制到Java对象中,只有这样才能保证JNI的正确性。这点与JVM中分区的垃圾管理器完全不同。
虽然可以通过复制内存的办法来避免这个问题,但这将严重影响性能。
在OpenJ9不断的演化中,提出了一种所谓两次映射(Double-Mapping)的概念,这个方法的思路是给数组对象分配一个连续的地址,利用操作系统的虚拟地址方式,再将其中的片段映射到分区中,如图6-28所示。
该方法通过两次映射技术将不连续的内存呈现为连续的地址,从而解决了Arraylet在JNI中使用低效的问题。
回收的区别
G1中Minor GC仅仅回收新生代,Mixed GC回收新生代空间和部分老生代。统一采用复制算法。
Balanced GC中的部分回收采用复制算法、标记清除压缩的复合算法。复制算法回收效率高、内存利用率低,而标记清除压缩算法回收效率低、内存利用率高。通常采用复制算法进行回收,而在复制算法中采用宽度优先遍历或者层次遍历的回收算法。
当内存不足或者出现一些特殊情况时,例如在JNI中使用Critical的API,要求内存不能移动,此时采用标记压缩算法就更为合理。
,Balanced GC可以通过参数控制复制和标记清除压缩的执行情况,例如设置参数,要求执行复制算法以后执行标记清除压缩,或者执行标记清除压缩以后执行复制算法。
在Balanced GC中,对于混合回收来说,除了回收新生代空间之外,对过渡代空间中的哪些代可以回收则是通过参数控制的,默认是回收过渡代空间中的第1代,而对于老生代空间总是不回收。
并发标记的区别
G1采用SATB的增量并发标记算法,通过写屏障保证引用的正确性。
Balanced GC采用典型的分阶段的增量并发标记算法,也是通过写屏障保证引用的正确性。
在G1中并发标记由Minor GC触发,并发标记是以Minor GC回收后的Survivor分区作为根,在对根进行标记的时候,不能再启动Minor GC。当并发标记执行的时候不能启动新一轮的并发标记。
在Balanced GC中,并发标记和复制回收之间并没有明确的依赖关系。
Balanced GC中通过参数控制复制回收和并发标记的触发情况,即设置复制回收和并发标记的执行比例,默认是1 1,即每执行一次复制回收就会启动一次并发标记。注意,复制回收和并发标记使用相同的触发逻辑,都是在对象分配时决定是否启动。由于并发标记是分阶段的增量标记,可以通过参数设置每一阶段运行的时间,当运行超时后,并发标记中止;当新一轮并发标记启动后,会继续执行并发。Balanced GC并发标记的状态转换图如图6-29所示。
在图6-29中,发生超时后判断通常在任务完成后进行,当超时发生后会结束并发标记的执行,新一轮并发标记启动后从超时返回后的状态开始执行。
Balanced GC的并发标记对复制有影响,混合回收时必须要求过渡代空间的分区完成标记。Balanced GC与G1相比还有一个不同的点是,并发标记执行完成后,可以执行并发清除操作,而G1中也有清除阶段,是并行执行的,清除仅仅是针对完全空的老生代分区,让其立即被重用。
Full GC与Balanced GC的区别
G1中Full GC采用并行、串行标记压缩算法,而Balanced GC采用并行的标记清除压缩算法,两者在执行过程中都会导致应用暂停。因为Balanced GC中存在一个额外的清除阶段,所以需要遍历堆空间一次。Balanced GC在清除之后,整个堆空间都是活跃对象,所以在执行压缩时可以选择复制或者就地压缩。
本文给大家讲解的内容是JVM垃圾回收器详解垃圾优先,扩展阅读OpenJ9中的Balanced GC介绍
- 下篇文章给大家讲解的内容是JVM垃圾回收器详解Shenandoah,内存模型及并发标记设计
- 感谢大家的支持!