ParNew + CMS 两个垃圾回收器对新生代和老年代进行垃圾回收的运行过程中,都会或多或少产生 STW 现象,对系统的运行是有一定影响的。
而为了减少 STW ,G1垃圾回收器应运而生,他可以提供比 “ParNew + CMS” 组合更好的垃圾回收的性能。
G1垃圾回收器可以同时回收新生代和老年代的对象,不需要两个垃圾回收器配合起来运作。
G1垃圾回收器最大的特点,是把Java堆内存拆分为多个大小相等的 Region(范围)。
虽然G1也会有新生代和老年代的概念,但只是逻辑上的概念。
从内存结构上来说,新生代可能包含了某些Region,老年代可能包含了某些Region。如下图:
而G1最大的一个特点,就是可以设置一个垃圾回收的预期停顿时间。
也就是说可以指定,希望G1在垃圾回收的时候保证,在1小时内由G1垃圾回收导致的 STW 时间(系统停顿时间) 不能超过1分钟。
前面对JVM优化的思路,最终目的都是为了减少Minor GC和Full GC,从而减少GC带来的系统停顿,避免影响系统处理请求。
但现在我们可以给G1指定,在一个时间内,垃圾回收导致的系统停顿时间不能超过多久,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的核心设计思路。
在G1中,每一个 Region 可能属于新生代,也可能属于老年代的。
刚开始 Region 可能谁都不属于,然后接着就分配给了新生代,然后放了很多属于新生代的对象,接着就触发了垃圾回收这个 Region,如下图:
然后下一次同一个 Region 可能又被分配给了老年代,用来防老年的的长生存周期的对象,如下图:
所以,在G1对应的内存模型中, Region 随时会属于新生代也会属于老年代,所以没有所谓新生代给多少内存,老年代给多少内存的说法。
新生代和老年代各自的内存区域是不停的变动的,由 G1 自动控制。
G1垃圾回收器的设计思想中有 Region 的概念,并对 Region 进行划分,然后 Region动态转移给新生代或老年代,按需分配。
触发垃圾回收的时候,可以根据设定的预期系统停顿时间,来选择最少回收的时间和最多回收对象的 Region 进行垃圾回收,保证 GC对系统停顿的影响在可控范围内,同时还能尽可能回收更最的对象。
(1)每一个Region 都可以是新生代,采用的是复制算法。存在一个比例,不会让所有 Region 都被使用掉,所以不存在所有的 Region 都存放了新生代或老年代,而导致没有可用的 Region 的情况发生。
(2)如果需要一个大对象有 100MB,然而每个 Region 的大小只有 20MB,此时这个大对象可以横跨多个 Region 。
(3)Region 被回收后 G1是如何解决内存碎片化的?
答: Region 回收采用复制算法,就是一个 Region 里的存活对象迁移到其他 Region 里,然后直接回收那个 Region 剩余的垃圾对象。所以不存在内存碎片化的问题。