深入理解Java虚拟机(五)——垃圾收集器

274 阅读7分钟

垃圾收集器概述

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。HotSpot虚拟机提供了多款垃圾收集器,他们各有特点,但没有最好、最优的垃圾收集器,我们要做的就是根据实际情况选择最合适的垃圾收集器。

新生代垃圾收集器

Serial收集器

1.Serial收集器是单线程收集器,它只会占用一个CPU或线程去完成垃圾收集工作。

2.Serial收集器在工作时,必须暂停所有其他线程的运行。

3.对于单CPU系统来说,单线程的Serial在工作时不需要进行线程上下文的切换,因此工作效率是最高的。

4.对于运行在虚拟机的桌面应用(客户端)来说,所需的内存较小,相应地GC所需的时间就少,用户不会明显感到因GC产生的卡顿,因此Serial收集器是个不错的选择。

ParNew收集器

1.ParNew收集器是Serial收集器的多线程版本。

2.和Serial收集器相比,ParNew收集器更适用于多CPU环境。在单CPU环境下,ParNew收集器因为需要切换线程上下文,其收集效率不如Serial收集器。

3.Parnew收集器启动多条线程完成垃圾收集工作,在多CPU环境下,缩短了垃圾收集所需的时间,也就减少了用户线程停顿的时间。

Parallel Scavenge收集器

1.和其他追求尽可能缩短用户线程停顿时间的收集器相比,Paraller Scavenge收集器更关注吞吐量(所谓吞吐量就是用户线程运行时间比上CPU运行总时间)。

2.低停顿时间能提升用户体验,高吞吐量能提高CPU利用率,尽快完成计算任务,因此Parallel Scavenge收集器适用于不需要太多交互的任务。

3.Parallel Scavenge收集器提供的影响吞吐量的参数:

  • ●-XX:MaxGCPauseMillis:最大垃圾收集时间,收集器将尽可能使收集时间不超过该值。
  • ●-XX:GCTimeRadio:用于直接设置吞吐量的大小。
  • ●-XX:+UseAdaptiveSizePolicy:这是一个开关参数,能够开启自适应策略。

4.Parallel Scavenge自适应策略:

  • ●虚拟机能够根据系统运行状况,动态调整相关参数以降低停顿时间或提高吞吐量。
  • ●开启自适应策略前,我们需要把基本的内存数据设置好,比如最大堆、MaxGCPauseMillis(更关注停顿时间)GCTimeRadio(更关注吞吐量),这些参数给虚拟机一个优化目标,之后具体的细节参数调整就就给虚拟机了。
  • ●自适应策略是Parallel Scavenge收集器和ParNew收集器的一个重要区别。

5.设置MaxGCPauseMillis能缩短GC停顿时间是以牺牲新生代和吞吐量的代价换来的:当新生代调小后,GC动作所需的时间变小,也即GC停顿时间缩短,但GC动作则更容易被触发。

老年代垃圾收集器

Serial Old收集器

1.Serial Old收集器是Serial收集器的老年代版本,它同样是单线程收集器,使用标记-整理算法。

2.Serial Old收集器的主要意义是给客户端使用的。

3.Serial Old收集器还可作为CMS收集器的后备方案,在CMS收集失败时换用。

Parallel Old收集器

1.Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

2.在追求高吞吐量以及CPU资源敏感的应用场景中,可使用Parallel Old收集器、Parallel Scavenge收集器组合。

CMS收集器

1.CMS收集器是一款追求短停顿时间的收集器,基于标记-清除算法,运作过程如下:

  • 初始标记。初始标记需要Stop The World,作用是仅使用一条线程标记出GC Roots能直接关联的对象,速度很快。
  • 并发标记。并发标记期间,从GC Roots进行可达性分析并向下搜索形成引用链,并且用户线程和GC线程是并发运行的。这个阶段耗时较长。
  • 重新标记。需要Stop The World,使用多条线程,修正并发标记期间因用户线程运行而导致的标记发生变动的那部分对象的标记记录。这个过程耗时比初始标记长,但远小于并发标记。
  • 并发清除。仅使用一条线程清除无用对象,期间用户线程和GC线程是并发运行的。这个过程非常耗时。

2.CMS收集器的缺点:

  • ●对CPU资源敏感。在并发期间,原本用于执行用户线程的CPU需要分出一部分时间用于GC线程的执行,这将导致吞吐量降低。
  • ●无法处理浮动垃圾,可能导致并发模式失败,从而调用另一种Full GC。浮动垃圾指的是在并发清除阶段,用户线程运行而产生的垃圾无法在当次收集中清除,需要等到下次GC才能被清除,这部分垃圾就叫浮动垃圾。又因为用户线程还在运行,老年代需留有足够内存才能保证运行正常,若老年代内存不够,则将导致并发模式失败,虚拟机将启动后备的垃圾收集器Serial Old重新收集老年代,这样停顿时间也就更长了。
  • ●内存碎片问题。采用标记-清除算法的收集器会在收集完成后会产生内存碎片,当老年代中内存足够大却仍找不出一块空闲空间存储大对象时,将不得不提前触发一次Full GC。解决这个问题的策略如下:
    • ●开启-XX:+UseCMSCompactAtFullCollection开关参数(默认开启)。作用的CMS收集器在不得不触发Full GC之前将内存碎片合并在一起,这个过程是无法并发的,一次停顿时间将变长。
    • ●设置-XX:CMSFullGCsBeforeCompaction参数,表示在经过多少次没有启动内存碎片合并的Full GC后,下一次Full GC启动内存碎片合并。

G1收集器

1.G1收集器的特点如下:

  • ●并行和并发(在垃圾收集器的语境下,并行指的是GC线程并行执行,用户线程处于等待状态;并发指的是GC线程以及用户线程同时执行)。
  • ●追求短GC停顿时间。
  • ●面向服务端。
  • ●Java堆被分为多个大小相等的Region,每个Region里仍有分代的概念,与其他收集器不同的是,Region的新生代和老年代不再是物理隔离,而是不连续的分布在Region里。
  • ●可预测停顿时间。
  • ● 从整体上看,G1采用标记-整理算法;从局部上看(Region之间),G1采用复制算法。

2.G1收集器以及其他垃圾收集器避免全堆扫描的策略是使用Remembered Set。

3.G1收集的过程如下(前几个步骤和CMS的运作过程一样):

  • 初始标记。初始标记需要Stop The World,作用是仅使用一条线程标记出GC Roots能直接关联的对象,速度很快。
  • 并发标记。从GC Roots进行可达性分析并向下搜索形成引用链,并且用户线程和GC线程是并发运行的。这个阶段耗时较长。
  • 最终标记。需要Stop The World,使用多条线程,修正并发标记期间因用户线程运行而导致的标记发生变动的那部分对象的标记记录。
  • 筛选回收。对各个Region的回收价值和成本进行排序,根据用户制定的GC时间设置回收计划。