GC Collectors(copy)
作者: 一字马胡
最近在学习GC相关内容,总觉得写过GC相关的文章,然后去翻了一下写过的文章集,果然发现了一篇自己都觉得不可思议的文章:浅谈JVM中的垃圾回收,这篇文章信息量较大,但是止于概念,虽然说得很多,包括JVM运行时内存布局、JVM堆的分代、GC的概念及各种GC算法的介绍,并且介绍了多个GC的运行原理,甚至还包括了JVM调优的相关内容。当然,那篇文章是在读《深入理解java虚拟机》的时候总结的,也包括从其他地方引用了一些内容,草草总结起来的一篇长文,质量算不上很高,但是也算是学习JVM/GC的第一篇文章吧。
本篇文章内容纯粹是从这里转译过来的,这篇文章介绍了多个GC,以及他们是如何搭配工作的,很有实际意义。
GCs这张图高度概括了多种GC,以及各个GC工作在新生代还是老年代,以及GC可以怎么搭配,图中黄色区域为新生代,灰色为老年代,每一个蓝色的方块代表一种GC,两个蓝色的方块之间连线代表他们可以相互搭配,没有连线的两个GC是不能相互配合的,图中仅有一个特殊的蓝色的方块,里面的内容为问号,其实代表的是G1 GC,除了这个特殊的蓝色方块之外,其他的方块内部都描述了GC的名称,下面简要介绍每一个GC。
- “Serial”:是一个会“stop-the-world”的单线程年轻代GC,使用copying算法。在进行垃圾收集的时候需要Stop-the-world直到Serial GC工作完成。
- “ParNew”:是“Serial”的多线程版本,工作在年轻代,使用copying算法。属于并行GC(非并发GC,CMS属于并发GC)。
- “Parallel Scavenge”:是一个会“stop-the-world”的年轻代GC,使用复制算法,他和“ParNew”有几点区别,首先,它不可以和CMS搭配使用;其次,“Parallel Scavenge”被设计成是“吞吐量优先”的GC,所以我们可以给GC设定一些目标参数,比如可以使用“-XX:MaxGCPauseMillis”参数设置一个期望的GC停顿时间(单位是毫秒),但是,降低GC停顿时间的代价就是吞吐量降低,GC管理的年轻代缩小,young GC频率将变大,所以系统的吞吐量(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间))会变低。当然还可以使用“-XX:GCTimeRatio”参数设置吞吐量,你可以设置一个0-100的数值来代表你期望的系统吞吐量。该GC还可以使用“-XX:+UseAdaptiveSizePolicy”参数来让GC自己调整eden、from、to区域及对象晋升老年代阈值等参数。(内容参考)
- “Serial Old”:老年代GC,使用“mark-sweep-compact”算法,属于“Serial”的老年代GC版本,单线程运行,会“stop-the-world”。这个算法用处还是比较大的,可以看到这个老年代GC可以和“Serial”和“ParNew”以及“Parallel Scavenge”搭配使用(也就是可以和所有yong gc搭配使用),而且还可以和CMS搭配,是CMS发生“Concurrent Mode Failure”异常的时候的后备“old gc”。
- “CMS”:这是一种并发GC,也就是GC线程和用户线程可以同时运行,这和并行GC是有本质区别的,并行GC如ParNew仅仅是使用了多线程来做GC的工作,在GC线程在工作的时候,用户线程需要暂停,等待GC完成之后再继续运行。“CMS”被称为“低延时GC”,使用该GC将会使得GC停顿(stop-the-world)时间非常短暂,但是带来的负面影响也有很多,比如CMS工作时会比其他GC占用更多的内存和cpu资源,并且默认情况下该GC不支持内存压缩,可能会发生因内存碎片问题而导致不得不压缩内存的情况,这种情况下可能会发生较长时间的“stop-the-world”时间,在使用该GC的时候需要考虑好内存碎片问题。
- “Parallel Old”:该算法是“Parallel Scavenge”的老年代版本,使用“mark-compacting”算法来做GC工作,该GC只能和“Parallel Scavenge”搭配使用。
- “G1”: G1是目前为止最复杂、也是最先进的GC,在CMS 算法中,GC 管理的内存被划分为新生代、老年代和永久代/元空间。这些空间必须是地址连续的。在G1算法中,采用了另外一种完全不同的方式组织堆内存,堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,Region的大小可以通过 -XX:G1HeapRegionSize 参数指定,如果没有设置,默认把堆内存按照2048份均分,最后得到一个合理的大小。在G1中,还有一种特殊的区域,叫 Humongous 区域。 如果一个对象占用的空间超过了分区容量 50% 以上,G1 收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1 划分了一个 Humongous 区,它用来专门存放巨型对象。该GC适合在超大堆的JVM里面使用。
总结:年轻代GC都采用copying算法,而老年代则没有使用copying算法,老年代GC管理的堆空间较大,一般需要考虑内存碎片问题(因为没有采用copying算法),特别是CMS。并发GC和并行GC的区别是是否可以在GC线程工作的同时运行用户代码。下面是一些GC设置产生的GC组合情况。
设置 | 年轻代GC | 老年代GC |
---|---|---|
-XX:+UseSerialGC | Serial | Serial Old |
-XX:+UseParNewGC | ParNew | Serial Old |
-XX:+UseConcMarkSweepGC | ParNew | CMS + Serial Old |
-XX:+UseParallelGC | Parallel Scavenge | Serial Old |
-XX:+UseParallelOldGC | Parallel Scavenge | Parallel Old |
-XX:+UseG1GC | G1 | G1 |
整体来说,GC可以分为如下几类:
- Serial Garbage Collector
- Parallel Garbage Collector
- CMS Garbage Collector
- G First Garbage Collector(G1 GC)
其中,Serial GC常用于client JVM,或者比如Serial Old在CMS发生“Concurrent Mode Failure”异常时作为备选的“Old GC”;server JVM不建议使用Serial GC;Parallel GC称为“吞吐量优先”GC,可以使用“-XX:MaxGCPauseMillis”和“-XX:GCTimeRatio”来让GC按照我们的预期工作;CMS GC称为“低延时GC”,G1 GC是目前最先进的GC;对于Server JVM,如果是一些后台任务,交互比较少的应用(比如批量处理任务),建议使用Parallel GC,如果是存在交互并且对响应时间敏感的话建议使用CMS GC(比如APP后台)。