垃圾收集器总结
各种收集器组合使用的参数
Serial + CMS
注意不能使用-XX:+UseConcMarkSweepGC -XX:+UseSerialGC
,否则会报Conflicting collector combinations in option list; please refer to the release notes for the combinations allowed
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC
ParNew + CMS
-XX:+UseConcMarkSweepGC
或
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
Serial + Serial Old
-XX:+UseSerialGC
ParNew + Serial Old
不建议使用的组合。Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
注意不能使用-XX:+UseParNewGC -XX:+UseSerialGC
,否则会报Conflicting collector combinations in option list; please refer to the release notes for the combinations allowed
-XX:+UseParNewGC
Parallel Scavenge + Serial Old
由于老年代的收集是单线程的,无法获得吞吐量最大化的效果,这种组合还不一定有 ParNew + CMS 给力
不知道怎么设置
Parallel Scavenge + Parallel Old
注重吞吐量以及CPU资源敏感的场景下使用
-XX:+UseParallelGC
或
-XX:+UseParallelOldGC
或
-XX:+UseParallelGC -XX:+UseParallelOldGC
或直接不写,默认使用的就是Parallel Scavenge + Parallel Old
G1
-XX:+UseG1GC
串行收集器
Serial
- 单线程收集器,回收时必须暂停其他所有工作线程.
- 它是虚拟机在client模式下的默认新生代收集器,因为在用户桌面应用场景中,分配给虚拟机管理的内存一般不会很大,收集几十兆甚至一两百兆的新生代,停顿时间可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的.
Serial Old
- 它是Serial收集器的老年代版本,使用"标记-整理"算法,这个收集器的主要意思也是在于给client模式下的虚拟机使用
- 在Server模式下,它主要还有两大用途:
一个用途是再jdk1.5之前与 Parallel Scavenge 收集器搭配使用
另一个用途是作为CMS收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用.
并行收集器
ParNew
- 其实不需要显式开启
ParNewGC
,当使用-XX:+UseConcMarkSweepGC
时年轻代默认使用的就是ParNewGC
,如果要显式开启ParNewGC
那么必须指定-XX:+UseConcMarkSweepGC
,否则默认老年代收集器使用的是SerialGC
,这是不建议使用的组合。 - 它是Serial收集器的多线程版本,只有Serial收集器和ParNew收集器能与CMS收集器配合工作.
- 控制参数和Serial一样,比如-XX:SurvivorRatio,-XX:PretenureSizeThresold,-XX:HandlePromotionFailure等
- 当使用-XX:UseConcMarkSweepGC选项后,默认使用ParNew作为新生代的收集器,也可以使用-XX:UseParNewGC选项强制指定
- 默认开启的收集线程数等于CPU数量,如果CPU非常多,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数
Parallel Scavenge
- 只要执行
-XX:+UseParallelGC
和-XX:+UseParallelOldGC
其中一个,另一个默认激活。Parallel Scavenge + Parallel Old
- 它是一个新生代的收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样
- 它的关注点在于吞吐量,吞吐量的计算方式: 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
- 无法与CMS收集器配合使用
- 它提供两个参数用于精确地控制吞吐量
-
-XX:MaxGCPauseMillis
最大垃圾收集停顿时间(大于0的毫秒数).收集器将尽可能不超过此值,如果此值设置的太小,那么代价就是缩小新生代的空间来换取停顿时间的更短,这将导致垃圾收集变的很频繁 -
-XX:GCTimeRatio
设置吞吐量大小,也就是垃圾收集时间占总时间的比率(大于0且小于100的整数).默认值是99,即 1/(1+99) 也就是允许最大1%的垃圾收集时间
-
- GC自适应的调节策略
-
-XX:+UseAdaptiveSizePolicy
打开GC自适应调节(默认开启).当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThresold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调节这些参数以提供最合适的停顿时间或最大吞吐量.只需要把基本的内存设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis或GCTimeRatio参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了.
-
Parallel Old
- 它是 Parallel Scavenge 收集器的老年代版本,使用多线程的"标记-整理"算法
- 这个收集器是在jdk1.6才提供的,在jdk1.6之前,如果新生代选择了 Parallel Scavenge ,那么老年代只能选择 Serial Old
并发标记清除收集器
CMS (Concurrent Mark Sweep)
- 当开启CMS时
-XX:+UseConcMarkSweepGC
默认使用的年轻代收集器为ParNew。ParNew + CMS + Serial Old
- Concurrent Mark Sweep 并发标记清除收集器,它是一个多线程并行回收,采用标记-清除算法的老年代gc。
- CMS工作的主要步骤:初始标记、并发标记、重新标记、并发清除。其中初始标记和重新标记是独占资源的(stop the world)。另外两个步骤是和应用线程一起执行的。
- 初始标记、并发标记和重新标记都是为了标记出需要回收的对象。
- 初始标记仅仅是标记一下GC Roots能直接关联到的对象,并将它们的字段压入扫描栈(marking stack)中等到后续扫描,速度很快。
- 并发标记阶段就是对初始标记到的对象进行Tracing标记出存活对象。它表现为不断从扫描栈取出引用递归扫描整个堆里的对象图。每扫描到一个对象就会对其标记,并将其字段压入扫描栈。重复扫描过程直到扫描栈清空。
- 重新标记就是在完成并发标记后,对那些在并发标记期间重新加到引用链上的对象进行标记。
- 并发清除,则是在标记完成后,正式回收垃圾对象,并重新初始化CMS数据结构和数据,为下一次垃圾回收做准备。
- 由于CMS收集器和应用程序线程并发执行,相互抢占CPU,所以CMS执行期间对程序吞吐量将造成一定影响。
- CMS默认启动的线程数时
(CPU数量+3)/4
,也可以通过-XX:ParallelCMSThreads
参数手工设定CMS的线程数量。当CPU资源比较紧张时,受到CMS收集器线程的影响,应用系统的性能在垃圾回收阶段可能会非常糟糕。 - 由于CMS不是独占式的,所以在应用程序工作过程中,又会不断产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。同时,因为应用程序没有中断,所以在CMS回收过程中,还应该确保应用程序有足够的内存可用。因此,CMS收集器不会等待堆内存饱和时才进行垃圾回收,而是当堆内存使用率达到
-XX:CMSInitiatingOccupancyFraction
(默认68,即68%)指定值时,便开始回收,以确保应用程序在CMS工作过程中,依然有足够的空间支持应用程序运行。 - 如果应用程序的内存使用率增长很快,在CMS的执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,JVM将启动老年代串行回收器进行垃圾回收(此时,应用停顿时间就很长了)。
- 根据应用程序的特点,可以对
-XX:CMSInitiatingOccupancyFraction
进行调优。如果内存增长缓慢,则可以设置一个较大的值,大的阈值可以有效降低CMS的出发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁出发老年代串行收集器。 - 由于CMS采用的是标记清除算法,所以会产生大量内存碎片。可以使用
-XX:+UseCMSCompactAtFullCollection
开关(默认为开)使CMS在垃圾收集完成后,进行一次内存碎片整理。内存碎片整理不是并发执行的。还可以使用-XX:CMSFullGCsBeforeCompaction
(默认为0)参数可以用于设定进行多少次CMS回收后,进行一次内存压缩。 - CMS只能和新生代的Serial收集器或ParNew收集器配合使用
G1
G1具备如下特点:
- 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多CPU(CPU或CPU核心)来缩短STW停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
- 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
- 空间整合:与CMS的“标记——清理”算法不同,G1从整体来看是基于“标记——整理”算法实现的收集器,从局部(另个Region之间)上来看是基于“复制算法实现的”,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC。
- 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region(不需要连续)的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收获得的空间大小以及回收所需时间的经验值),在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以获取尽可能高的回收效率。
G1实现细节远没有想象中那样简单:把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?听起来顺理成章,再仔细想想就很容易发现问题所在:Region不可能是孤立的。一个对象分配在某个Region中,它并非只能被本Region中的其他对象引用,而是可以与整个Java堆任意的对象发生引用关系。那在做可达性判定对象是否存活的时候,岂不是还得扫描整个Java堆才能保证准确性?这个问题其实并非在G1中才有,只是在G1中更加突出而已。在以前的分代收集中,新生代的规模一般都比老年代要小很多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象时也面临相同的问题,如果回收新生代时也不得不同时扫描老年代的话,那么MinorGC的效率可能下降不少。
在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:
- 初始标记(Initial Marking)
- 并发标记(Concurrent Marking)
- 最终标记(Final Marking)
- 筛选回收(Live Data Counting and Evacuation)
初始标记阶段仅仅是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Nest Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时很长,但可与用户程序并发执行。而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来指定回收计划,从Sun公司透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为指挥手一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。