高手的存在,就是让服务10亿人的时候,你感觉只是为你一个人服务......

浅谈JVM垃圾收集器

目录
  1. 1. 垃圾收集算法
    1. 1.1. 标记-清除算法(Mark-Sweep)
    2. 1.2. 复制算法(Copying)
    3. 1.3. 标记-整理算法(Mark-Compact)
    4. 1.4. 分代收集算法(Generational Collection)
  2. 2. 垃圾收集器
    1. 2.1. 垃圾收集器使用情况调查
    2. 2.2. 概念理解:
      1. 2.2.1. stop-the-world(STW)
      2. 2.2.2. 并行和并发
      3. 2.2.3. Minor GC和Major GC
      4. 2.2.4. 吞吐量
    3. 2.3. Serial收集器
    4. 2.4. ParNew收集器
    5. 2.5. Parallel Scavenge收集器
    6. 2.6. Serial Old收集器
    7. 2.7. Parallel Old收集器
    8. 2.8. CMS收集器(Concurrent Mark Sweep:并发标记清除)
  3. 3. 总结:

Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,

因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别,

并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。


垃圾收集算法

垃圾收集算法主要有3种:

标记-清除算法,如:CMS
复制算法,年轻代的基本上都是用copying算法
标记-整理算法,如:parallel old使用的该算法

至于分代收集算法,就是年轻代和年老代使用不同的算法进行组合。

标记-清除算法(Mark-Sweep)

这种垃圾收集算法思路非常简单,首先标记出所有需要回收的对象,然后回收所有需要回收的对象。
Alt text

但是这种算法有两个缺点:

效率问题:标记和清除两个过程的效率都不高
碎片问题:标记清除后会产生大量不连续的内存碎片,碎片大多会导致大对象得不到足够连续的内存空间,从而触发另一次垃圾回收。

复制算法(Copying)

这种算法会将内存空间分配成两块相同的区域A和B。
当内存回收的时候,将A中的内存块拷贝到B中,然后一次性清空A。
Alt text

这种算法的代价是将内存缩小为原来的一半。

现在的商业虚拟机都采用这种收集算法来回收年轻代。
有研究表明,新生代中的对象70%~95%都是“朝生夕死”,并不需要按照1:1的比例来划分内存空间。
比如Hotspot虚拟机的年轻代,分为eden区和survivor区(s1+s0),默认的比例大小是8:1,
也就是每次年轻代中可用的内存空间是整个年轻代容量的90%(80%+10%),只有10%的内存会被浪费。

标记-整理算法(Mark-Compact)

“标记-整理”算法中,标记过程和“标记-清除”算法中的“标记”过程一样,
但后续的动作不是直接清理不用的对象,而是将所有存活的对象都移动到一端,然后直接清理掉端边界以外的内存。

Alt text

分代收集算法(Generational Collection)

就是年轻代和年老代使用不同的算法进行组合。
年轻代中对象的存活率低,采用复制算法;
年老代中对象的存活率高,采用“标记-清理”或者“标记-整理算法”;


垃圾收集器

上面整理了几种垃圾收集算法只能算是内存回收的方法论,
具体的垃圾收集器就是内存回收的具体实现

对应Hotspot虚拟机,我们使用的垃圾收集器大概有7种:

Serial Garbage Collector
ParNew Garbage Collector
Parallel Scavenge Garbage Collector
Serial Old Garbage Collector
Parallel Old Garbage Collector
CMS Garbage Collector
G1 Garbage Collector

根据年轻代和年老代对象的存活情况,以及每种收集器特点,选择不同的收集器进行组合。

Alt text
如果两个收集器之间存在连线,说明它们可以搭配使用。

垃圾收集器使用情况调查

2013年的时候,Plumbr公司对垃圾收集器使用情况做了一次调查研究

CMS最受欢迎,我们公司大多数项目使用的也是CMS(B2B,低停顿)
Alt text


概念理解:

以下概念都是gc中经常遇到的,需要了解清楚。

stop-the-world(STW)

STW意味着 JVM 因为要执行GC而停止了用户应用程序的执行。

STW任何一种GC算法中发生,GC优化很多时候就是指减少Stop-the-world发生的时间。

并行和并发

并行(Parallel):左手写字、右手画画一起做,这叫并行。

并发(Concurrent):左手写完字,然后右手再画画,这叫并发。

可以参考:并行与并发的区别

Minor GC和Major GC

新生代GC(Minor GC):指发生在新生代的垃圾收集动作。

老年代GC(Major GC / Full GC):指发生在老年代的GC。

出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的)。

Major GC的速度一般会比Minor GC慢10倍以上。

吞吐量

吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。

虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。


Serial收集器

1. 特性:
单线程收集器
复制算法
整个过程Stop The World

Alt text

2. jvm 配置参数:
-XX:+UseSerialGC

3. 应用场景:
Serial收集器是虚拟机运行在Client模式下的默认新生代收集器

4. 优势:
简单而高效(与其他收集器的单线程比)
对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

ParNew收集器

1. 特性:
ParNew收集器其实就是Serial收集器的多线程版本,也是复制算法收集器。

Alt text

2. jvm 配置参数:
-XX:+UseParNewGC
-XX:ParallelGCThreads 限制线程数量

3. 应用场景:
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。

主要因为除了Serial收集器外目前只有它能与CMS收集器配合工作。

CMS收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

不幸的是,CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作。

所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。

4. Serial收集器 VS ParNew收集器:
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销。

该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。

然而,随着可以使用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。

Parallel Scavenge收集器

1. 特性:
Parallel Scavenge收集器是一个新生代收集器,
它也是使用复制算法的收集器,又是并行的多线程收集器。
Alt text

CMS等收集器的关注点在低停顿时间,而Parallel Scavenge收集器更关注吞吐量。

2. jvm 配置参数:
-XX:+UseParallelGC
-XX:MaxGCPauseMillis 设置最大垃圾收集停顿时间
-XX:GCTimeRatio 设置吞吐量大小

3. 应用场景:
适合用在后台运算而不需要太多交互的任务

4. Parallel Scavenge收集器 VS ParNew收集器:
Parallel Scavenge收集器与ParNew收集器的一个重要区别是它具有自适应调节策略。

GC自适应的调节策略:
Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。
当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,
虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
这种调节方式称为GC自适应的调节策略(GC Ergonomics)。

Serial Old收集器

1. 特性:
Serial Old是Serial收集器的老年代版本
单线程收集器
使用标记-整理算法

Alt text

2. jvm 配置参数:
-XX:+UseSerialGC

3. 应用场景:
Client模式
Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用。

Server模式
如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

Parallel Old收集器

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

2. jvm 配置参数:
-XX:+UseParallelOldGC

3. 应用场景:
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。


以上所有的收集器当中,当执行GC时,都会stop the world,但是下面的CMS收集器却不会这样。

CMS收集器(Concurrent Mark Sweep:并发标记清除)

1. 特性:
以获取最短回收停顿时间为目标的收集器
回收过程是与用户线程一起并发执行
标记-清除算法

Alt text

2. jvm 配置参数:
-XX:+UseConcMarkSweepGC

3. 应用场景:
适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。

4. 垃圾收集过程:

初始标记(CMS initial mark stop the world)
这是CMS中两次stop-the-world事件中的一次。
它有两个目标:一是标记老年代中所有的GC Roots;二是标记被年轻代中活着的对象引用的对象。
Alt text

并发标记(CMS concurrent mark 和用户线程一起)
这个阶段会遍历整个老年代并且标记所有存活的对象,从“初始化标记”阶段找到的GC Roots开始。
并发标记的特点是和应用程序线程同时运行。并不是老年代的所有存活对象都会被标记,因为标记的同时应用程序会改变一些对象的引用等。
Alt text

重新标记(CMS remark stop the world)

这个阶段是CMS中第二个并且是最后一个STW的阶段。
该阶段的任务是完成标记整个年老代的所有的存活对象。

这个阶段的时间比较长,可以开启-XX:+CMSScavengeBeforeRemark选项,强制remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。

并发清除(CMS concurrent sweep 和用户线程一起)
和应用线程同时进行,不需要STW。
这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。

Alt text

5. CMS收集器的缺点:
CMS是一款基于“标记—清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。

空间碎片过多时,将会给大对象分配带来很大麻烦。
往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。


总结:

虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。

因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。

所以,在进行收集器选择的时候,需要进行大量的压测与调优,选择出最符合自身业务的收集器。