您的位置:首页 > 编程语言 > Java开发

垃圾收集器

2017-09-20 10:28 176 查看
记录JVM学习

参考:周志明的《深入了解Java虚拟机》

垃圾收集器就是内存回收算法的具体实现。

垃圾收集器针对新生代和老年代分别采用不同算法实现,新生代主要采用“复制算法”,老年代主要采用“标记-清除”或“标记-整理”算法。

主要术语:

并发(Concurrent)和并行(Parallel)在垃圾收集器的上下文语境中解释如下:

并行(parallel):

指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。也就是“stop the word”.

并发(Concurrent):

指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上

新生代收集器:Serial,ParNew,Parallel Scavenge

Serial收集器:

这个收集器是一个单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。他依然是虚拟机运行在Client模式下的默认新生代收集器。

ParNew收集器:

是Serial收集器的多线程版本,运行在Server模式下的虚拟机中首选的新生代收集器,ParNew收集器也是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定它。在CPU非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

Parallel Scavenge收集器:

它也是使用复制算法的收集器,它的目标是达到一个可控制的吞吐量(Throughput),吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

**停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。**Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一些,垃圾收集发生更频繁一些,停顿时间的确在下降,但吞吐量也降下来了。

-XX:+UseadaptiveSizePolicy,GC自适应策略调节开关,打开之后,就不需要手工指定新生代的大小(-Xmn),Eden与Survivor区的比例(-XX:SurvivorRatio),晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,只需要把基本的内存数据设置好(如-Xmx设置最大堆时间),然后使用MaxGCPauseMillis参数或GCTimeRatio参数给虚拟机设立一个优化目标,具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavege收集器与ParNew收集器的一个重要区别。

老年代收集器:

Serial Old收集器:单线程收集器,标记-整理

主要意义在于给Client模式下的虚拟机使用,在Server模式下:

1. 在JDK 1.5之前与Parallel Scavenge收集器搭配使用(其实Parallel Scavenge收集器本身有PS MarkSweep收集器来进行老年代收集,PS MarkSweep与Serial Old实现非常接近,所以以Serial old代替PS MarkSweep进行讲解)。

2. 作为CMS收集器的后备方案,在发生Concurrent Mode Failure时Full GC使用。

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用“标记-整理”

在注重“吞吐量优先”以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

CMS(Concurrent Mark Sweep)收集器,标记-清除

获取最短回收停顿时间为目标的收集器 。

适用于重视服务的响应速度,希望系统停顿时间最短,已给用户带来较好的体验。

整个做策划给你分为4个步骤,包括:

1. 初始标记(CMS initial mark):标记“GC Roots”,需要“stop the world”,时间短

2. 并发标记(CMS concurrent mark):进行GC RootsTracing的过程

3. 重新标记(CMS remark):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要“stop the world”.

4. 并发清除(CMS concurrent sweep)

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起并发执行。它虽然不会导致用户线程停顿,但是会因为占用了一部分(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时在清理掉。这一部分垃圾就称为“浮动垃圾”。由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“ConcurrentMode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

最后由于时“标记-清除”算法,会产生大量空间碎片,就会给打对象分配带来很大麻烦,无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC.

G1(Garbage-First)收集器 整体上是“标记-整理”,局部(两个Region)是”复制”

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1时,Java堆的内存布局就与其他收集器有很大差别,他将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region的集合。

G1跟踪各个Region里卖弄的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的来由)。这种使用Region划分内存空间以及优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。 G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

如果不计算维护Remember Set的操作,G1收集器的运作可划分为一下几个步骤:

1. 初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象。需要停顿线程。

2. 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。

3. 最终标记(Final Marking):修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据黑帮到Remembered Set中,这阶段需要停顿线程。

4. 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。需要线程停顿。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息