年轻代收集器
Serial 收集器
Serial收集器是最基本、发展历史最悠久的收集器,使用复制垃圾收集算法。该收集器是一个单线程的收集器,但“单线程”并不意味着它只会使用一个CPU或一条收集线程去完成工作,更重要的是它在进行垃圾收集时,必须暂停所有的工作线程,直到其收集结束(Stop The World)。
它是虚拟机在Client模式下的默认新生代收集器。
优点:简单高效、对于单CPU环境而言,由于没有线程交互的开销,所以其可用获得最高的单线程收集效率。
收集过程:
-
遍历新生代中所有根对象,eden和from区的根对象将被复制到to区,形成TO对象集。
-
遍历更低内存代(新生代无更低内存代)和更高内存代对象(老年代)所有对象(实际中无需遍历所有,而是通过Card Table解决),如果这些对象有引用当前内存代的对象,则把对象复制到to区,形成TO对象集。
备注:**Card Table(卡表)**是一种数据结构用来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域。
在JVM中,一个Card的大小是512字节,在多个线程并行收集时,JVM通过ParGCCardsPerStrideChunk 参数设置每个线程每次扫描的Card数量,默认是256。相当于是把老年代分成许多strides,每个线程每次扫描一个stride,每个stride大小为512*256 = 128K,如果你的老年代大小为4G,那总共有4G/128K=32K个Strides。多线程在扫描这么多的strides时就涉及到调度和分配的问题,stride数量太多就会导致线程在stride之间切换的开销增加,进而导致GC暂停时间增长。
-
通过广度优先搜索算法遍历扫描活跃对象,即TO对象集。
-
如果Eden区中的对象的age达到“年龄阈值”,则不是将对象复制到to区,而是提升到高一代,晋升到老年代。
备注:MaxTenuringThreshold决定了晋升的最大阈值。每经历一次GC,对象的年龄就会+1。而到了某一时刻,如果对象的年龄大于设置的一个晋升的年龄阈值,该对象就会晋升到老年代中。对象的年龄最大只能是15,因为JVM中使用4个字节来表示对象的年龄。可以通过参数**-XX:MaxTenuringThreshold**设置其最大阈值。TargetSurvivorRatio影响运行过程中虚拟机计算的晋升阈值。
年轻代晋升到老年代的年龄阈值是由参数TargetSurvivorRatio和参数MaxTenuringThreshold影响的。
假设晋升阈值为n,那么一定满足以下条件:所有年龄<=n的对象大小之和 < survivor区的大小*TargetSurvivorRatio%
-
如果to区满了,eden区中的存活对象也将被提升到高一代,分配到老年代。
-
清除eden区和from区、交换from区和to区地址。
小结:
进行CG之前需要确定
- to区为空
- 老年代有足够的内存容纳新生代的所有对象
- 上述1-3步被称为可达性分析
ParNew 收集器
ParNew收集器其实是Serial收集器的多线程版本,,使用复制垃圾收集算法。除了使用多条线程进行垃圾回收之外,其余的行为与Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等一致。
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作;但在单个CPU环境中,不会比Serial收集器有更好的效果,因为存在线程交互开销。
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证能超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对于GC时系统资源的利用还是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多(譬如32个,现在CPU动辄就4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的环境下,可以使用**-XX:ParallelGCThreads**参数来限制垃圾收集的线程数。
并行和并发的区别
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。
Parallel Scavenge 收集器
Parallel Scavenge收集器是是一款年轻代的收集器,它使用复制垃圾收集算法。和ParNew一样,它也会一款多线程的垃圾收集器,但是它又和ParNew有很大的不同点。
Parallel Scavenge收集器和其他收集器的关注点不同。其他收集器,比如ParNew和CMS这些收集器,它们主要关注的是如何缩短垃圾收集的时间。而Parallel Scavenge收集器关注的是如何控制系统运行的吞吐量。这里说的吞吐量,指的是CPU用于运行应用程序的时间和CPU总时间的占比,吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间)。如果虚拟机运行的总的CPU时间是100分钟,而用于执行垃圾收集的时间为1分钟,那么吞吐量就是99%。
一般而言,在用户界面程序中,使用低延迟的垃圾收集器会有很好的效果,而对于后台计算任务的系统,高吞吐量的收集器才是首选。
Parallel Scavenge收集器还有一个参数:-XX:UseAdaptiveSizePolicy。这是一个开关参数,当开启这个参数以后,就不需要手动指定新生代的内存大小(-Xmn)、Eden区和Survivor区的比值(-XX:SurvivorRatio)以及晋升到老年代的对象的大小(-XX:PretenureSizeThreshold)等参数了,虚拟机会根据当前系统的运行情况动态调整合适的设置值来达到合适的停顿时间和合适的吞吐量,这种方式称为GC自适应调节策略。
GC自适应调节策略也是Parallel Scavenge收集器和ParNew收集器的一大区别。