6、HotSpot之垃圾收集器

来源:互联网 发布:阿里云ecs开放端口 编辑:程序博客网 时间:2024/05/22 14:28

在HotSpot虚拟机中,有7种作用于不同分代的收集器,如下图:

垃圾收集器

其中两个收集器之间存在的连线,即表示它们可以搭配使用。收集器所处区域,表示它属于新生代或者老年代收集器。

Serial收集器

这是最基本的收集器,是一个单线程收集器,单线程不是指它只会使用一个CPU或者一条收集线程去完成工作,而是它垃圾收集时,必须暂停所有工作线程,直到收集结束。如下图,展示的是Serial/Serial Old收集器的运行情况。
Serial和Serial Old运行示意图

直至现在,它依然是虚拟机运行Client模式下默认的新生代收集器。它简单高效,对于单个CPU环境来说,没有线程交互的开销,专心收集垃圾完全获得了最高的单线程回收效率。

在用户的桌面应用场景中,一般分配给虚拟机的内存也不会很大(仅新生代的内存只在几十M到一两百M之间),收集新生代的停顿时间可以控制在几十毫秒到一百多毫秒之内,是Client模式下的虚拟机是个不错的选择。

ParNew收集器

其实是Serial收集器的多线程版本,除了手机时使用多线程,其余的参数设置、收集算法、暂停所有线程,对象分配规则,回收策略等都和Serial一样。下图是ParNew和Serial Old运行示意图。

ParNew和Serial Old

它是运行在Server模式下的虚拟机首选的新生代收集器。目前只有ParNew收集器可以与CMS收集器(CMS是老年代收集器)配合工作。CMS收集器是真正意义上的第一款并发收集器,可以让垃圾收集线程和用户线程同时工作。

单线程的时候,ParNew的效率不会比Serial好,而随着CPU的增加,它的效率也会随之提高,它默认开启的线程数与CPU数量相同。线程数可以使用

-XX:ParallelGCThreads

来配置。

Parallel Scavenge收集器

Parallel Scavenge是一个新生代收集器,它也是使用复制算法。也是并行的多线程收集器。

它的关注点和别的收集器有点区别,别的收集器是尽可能缩短垃圾回收时用户线程的停顿时间,而它则是达到一个可控制的吞吐量。即CPU用于运行用户代码时间与CPU总时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

该收集器有三个参数配置:
- -XX:MaxGCPauseMillis
该参数允许一个大于0的毫秒数,收集器会尽量保证内存回收不超过这个设定值。如果设置的时间过小,收集器就会牺牲吞吐量时间和新生代空间来换取时间的缩短,当然,也会导致垃圾收集更频繁。

  • -XX:GCTimeRatio
    可以设置为一个大于0小于100的整数,该参数指的是垃圾收集时间占总时间的比率。

  • -XX:+UseAdaptiveSizePolicy
    是一个开关参数,该参数打开就不需要指定新生代和老年代的大小了,也不需要设置Eden与Survivor的比例。虚拟机会动态调整,对于收集器运行不太了解,可以选择使用该收集器,自适应调整策略。使用该参数只需要设置一个基本的内存数据,还有-XX:MaxGCPauseMillis和-XX:GCTimeRatio即可。

Serial Old收集器

是Serial收集器的老年代版本,它也是一个单线程收集器,使用“标记-整理”算法。它主要也是给Client下的虚拟机使用,在Server模式,有两大用途:
- 与Parallel Scavenge收集器搭配使用(在Parallel Scavenge本身有PS MarkSweep来进行老年代收集,不过它的实现与Serial Old非常接近)
- 做为CMS收集器的后备预案,在并发收集Concurrent Mode Failure时使用。

Parallel Old收集器

是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。在该收集器出现前,老年代因为都是单线程收集,无法充分利用服务器多CPU处理能力,吞吐量还不一定有ParNew+CMS给力,该收集器出现后,“吞吐量优先”收集器才算是有了真正的组合。在注重吞吐量与CPU资源敏感的场合,都可以先考虑使用Parallel Scavenge+Parallel Old收集器。
ParallelScavenge和ParallelOld

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。现在的Java应用集中在互联网站或者B/S系统的服务端上,较注重服务响应速度,停顿时间短,才能给用户带来较好的体验。

该收集器基于“标记-清除”算法,运行步骤主要有四个:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)

在初始标记和重新标记时需要停止用户线程。初始标记仅仅只是标记GC Roots能直接关联到的对象,速度快;并发标记就是进行GC RootsTracing的过程;重新标记则是为了修正并发标记期间因用户进行运作而产生改动的那一部分对象的标记记录,这一阶段的停顿时间会比初始标记稍长,但远比并发标记短。

整个阶段耗时最长的是并发标记和并发清除了,不过因为可以和用户线程一起工作,所以总体可以认为内存回收是和用户线程并发执行的。
运行示意图如下:
CMS运行示意图

不过CMS有三个明显缺点:

  • 对CPU资源非常敏感:虽然不会导致用户线程停顿,却会占用一部分线程而导致应用程序变慢,总体吞吐量降低。默认启动的回收线程数是(CPU数量+3)/4。也就是回收垃圾线程数不少于1/4的Cpu资源,随着CPU增多而下降。当然,当CPU不足四个的话,影响就很大了。
  • CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”而导致另一次Full GC的产生。因为并发运行,所以在标记过程,肯定还有不断的新垃圾产生,这部分只能留待下次GC再清理。所以,也就需要通过调整参数预留一部分空间给并发运行产生的新垃圾了。
  • 因为是使用“标记-清除”算法,所以会出现大量空间碎片,给大对象分配造成麻烦,解决这个问题需要开启-XX:UseCMSCompactAtFullCollection这个参数,当要进行FullGC时,就会把空间碎片也进行整理,还有一个-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多次FullGC后才进行一次空间碎片的整理压缩。如果为0,则每次都进行碎片整理。

G1收集器

是一款面向服务端应用的垃圾收集器。具有如下特点:

  • 并发与并行:能充分利用多CPU、多核环境的优势,缩短StopTheWorld(停止所有用户线程)的停顿时间,能通过并发的方式让Java程序继续执行。
  • 分代收集:G1可以自己独立管理整个GC堆不需要配合,不过还是保留了分代概念。
  • 空间整合:GC时能保证不产生内存空间碎片,收集后能提供规整可用内存。有利于程序长时间运行。不会因为大对象分配不到连续内存而促发下一次GC。
  • 可预测的停顿:会建立可预测的停顿时间模型,让使用者明确指定在一个长度为M毫秒的时间片段内,GC时间不超过N毫秒。

G1大致运行步骤有如下几步:
- 初始标记
- 并发标记
- 最终标记
- 筛选标记

和CMS不同的是,最终标记中,会把有发生变化的对象记录在线程Remembered Set Logs里面,最终标记阶段需要把该Logs的数据合并到Remembered Set中,需要停顿,但也可以并行执行。最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来制定回收计划。

G1流程示意图

  • 并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发:指用户线程与垃圾收集线程同时执行(不一定是并行,可能是交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
0 0
原创粉丝点击