1. ParNew + CMS的组合有哪些痛点?
ParNew + CMS 两个垃圾回收器对新生代和老年代进行垃圾回收的运行过程中,都会或多或少产生 STW 现象,对系统的运行是有一定影响的。
而为了减少 STW ,G1垃圾回收器应运而生,他可以提供比 “ParNew + CMS” 组合更好的垃圾回收的性能。
2. G1垃圾回收器
G1垃圾回收器可以同时回收新生代和老年代的对象,不需要两个垃圾回收器配合起来运作。
G1垃圾回收器最大的特点,是把Java堆内存拆分为多个大小相等的 Region(范围)。
虽然G1也会有新生代和老年代的概念,但只是逻辑上的概念。
从内存结构上来说,新生代可能包含了某些Region,老年代可能包含了某些Region。如下图:
而G1最大的一个特点,就是可以设置一个垃圾回收的预期停顿时间。
也就是说可以指定,希望G1在垃圾回收的时候保证,在1小时内由G1垃圾回收导致的 STW 时间(系统停顿时间) 不能超过1分钟。
前面对JVM优化的思路,最终目的都是为了减少Minor GC和Full GC,从而减少GC带来的系统停顿,避免影响系统处理请求。
但现在我们可以给G1指定,在一个时间内,垃圾回收导致的系统停顿时间不能超过多久,G1全权负责,保证达到这个目标。
这就意味着我们可以直接控制垃圾回收对系统性能的影响了。
3. G1是如何做到对垃圾回收导致的系统停顿可控的?
G1想要做到对停顿时间的可控,需要追踪每个Region里的回收价值。
也就是搞清楚每个 Region里的对象有多少是垃圾,如果对这个 Region 进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾?
如上图,G1通过追踪发现, 1个Region中的垃圾对象有10MB,回收他们需要耗费1秒钟,另外一个 Region 中的垃圾对象有 20MB,回收他们需要耗费 200毫秒。
然后在垃圾回收的时候,G1会发现最近一段时间内,比如1小时内,垃圾回收已经导致了几百毫秒的系统停顿了,现在又要执行一次垃圾回收,那么必须是回收上图中那个值需要 200ms就能回收掉 20MB垃圾的 Region 。
于是G1 触发一次垃圾回收,虽然可能导致系统停顿了 200ms,但是一下子回收了更多的垃圾,就是 20MB的垃圾,如下图:
所以说,G1可以做到让你来设定垃圾回收对系统的影响,它自己通过把内存拆分为大量小 Region,以及追踪每个 Region 中可以回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象。
这就是G1的核心设计思路。
4. Region 可能属于新生代也可能属于老年代
在G1中,每一个 Region 可能属于新生代,也可能属于老年代的。
刚开始Region 可能谁都不属于,然后接着就分配给了新生代,然后放了很多属于新生代的对象,接着就触发了垃圾回收这个 Region,如下图:
然后下一次同一个 Region 可能又被分配给了老年代,用来防老年的的长生存周期的对象,如下图:
所以,在G1对应的内存模型中, Region 随时会属于新生代也会属于老年代,所以没有所谓新生代给多少内存,老年代给多少内存的说法。
新生代和老年代各自的内存区域是不停的变动的,由 G1 自动控制。
5. 总结
G1垃圾回收器的设计思想中有 Region 的概念,并对 Region 进行划分,然后 Region动态转移给新生代或老年代,按需分配。
触发垃圾回收的时候,可以根据设定的预期系统停顿时间,来选择最少回收的时间和最多回收对象的 Region 进行垃圾回收,保证 GC对系统停顿的影响在可控范围内,同时还能尽可能回收更最的对象。
6. 扩展知识点
1、 每一个Region 都可以是新生代,采用的是复制算法。存在一个比例,不会让所有 Region 都被使用掉,所以不存在所有的 Region 都存放了新生代或老年代,而导致没有可用的 Region 的情况发生。
2、 如果需要一个大对象有 100MB,然而每个 Region 的大小只有 20MB,此时这个大对象可以横跨多个 Region 。
3、 Region 被回收后 G1是如何解决内存碎片化的?
答:Region 回收采用复制算法,就是一个 Region 里的存活对象迁移到其他 Region 里,然后直接回收那个 Region 剩余的垃圾对象。所以不存在内存碎片化的问题。