JVM 传统垃圾收集器

JVM 传统垃圾收集器

小龙 859 2020-04-25

介绍

JVM为了方便进行内存的管理,将整体的堆内存进行了划分,所以每一块堆内存空间都需要进行各自的GC回收处理,于是就有了GC回收算法,而对于算法就必须考虑单核CPU与多核CPU并行的时代区分,对于传统的算法可以完整应用在JDK 1.8及以前的各个JDK版本中

年轻代回收算法老年代回收算法
串行GC(Serial Copying)串行GC(Serial MSC)
并行回收GC(Parallel Scavenge)并行GC(Parallel MSC)
并行GC(ParNew)并行GC(CMS)

串行算法:在最初的JVM提供的运行环境基本上都属于单核CPU,既然是单核CPU,那么所有的回收(回收之前一定要进行标记)操作就按照顺序的方式进行处理;
并行算法:而到了多核CPU的时代,由于可以有多个处理单元,所以对于JVM垃圾的回收就可以采用并行运行的模式进行处理,自然速度就有所提升了。

一、年轻代的GC回收算法

年轻代执行的GC主要是MinorGC,并且随着新对象大量的创建,MinorGC一定会执行的很频繁,也就是说MinorGC是执行最多的GC,而相对老年代来讲,年轻代的回收相对容易一些,而来老年代中的对象都一致被使用到的,而且会很多,一旦发生了GC操作,那么会非常耗费时间,所以老年代GC(Full GC)执行的频率不会特别高。

1.1 串行GC(Serial Copying)

  • 扫描出新生代中存活的对象(有引用链相连的对象);
  • MinorGC将存活的对象复制到Survival(存活区)的S0/S1(From Space和To Space)区
  • 将S0或S1对换角色,这两块内存同时只会有一块内存在工作,另一块处于空闲状态,当进行MinorGC的时候,会将存活的对象全部负复制到空闲的那一块内存上,然后切换两块内存的状态,将之前在工作的那块内存清空,等待下一次GC处理,又会再一次进行切换;
  • MinorGC完成之后,将存活对象的年龄加1,将对象年龄达到阈值(默认15)的对象,放入老年代。

当伊甸园区内存不足的时候,会触发MinorGC操作,而后在伊甸园区将存活对象保存在存活区(S0和S1需要进行互换,同时永远有一块内存是空的),利用存活区实现对象晋级的处理操作。

1.2并行回收GC(Parallel Scavenge)

  • 算法:复制(Copying)清理算法;
  • 操作步骤:在扫描和复制时均采用多线程方式处理,并行回收GC为空间较大的年轻代回收提供了许多的优化
  • 优势:在多CPU的机器上其GC耗时会比串行方式短,适合多CPU、对暂停时间要求较短的应用。

GC13.png

串行的GC操作是只有一个线程进行扫描、标记、移动,那么整体的执行性能一定是非常差的,在硬件性能提升之后就采用了并行机制来提升GC的处理性能,串行最大的问题在于有可能带来STWStop It World)暂停时间过长,很明显为了提升GC处理性能,那么应该让暂停的时间越短越好,而且并行机制就可以减少暂停时间带来的影响。

1.3 并行GC(ParNew)

并行GC(ParNew)必须结合老年代CMS GC一起使用。因为在年轻代如果发生了MinorGC时,老年代也需要使用CMS GC同时处理(并行回收GC并不会做这些)。

CMS(Concurrent Mark-Sweep),是以牺牲吞吐量为代价获得最短回收停顿时间的垃圾回收器。对于在要求服务器响应速度的应用上,这种垃圾回收器非常适合。

二、老年代的GC回收算法

老年代的GC(MajorGC、Full GC)执行的次数一般较少,因为老年代里面所保存的对象基本上都属于常用对象,也就是说这些对象往往都有引用关系,很少会有垃圾对象的出现。

2.1 串行GC(Serial MSC):标记-清除-压缩(Mark-Sweep-Compact)

  • 对所有的垃圾信息进行标记,而后进行内存空间的释放,但是这样会产生内存碎片,所有可以利用压缩的形式将所有存活对象占用的空间往左端保存,空出一块完整连续的内存空间来。

串行机制使用单线程的模式来处理,所以会造成较长时间的STW(停顿)问题,串行的机制已经不再适合当今的任何一种回收处理机制。

2.2 并行GC(Parallel Mark Sweep、Parallel Compacting)

操作步骤

  • 将老年代内存空间按照线程个数划分为若干个子区域;
  • 多个线程并行对各自子区域内的存活对象进行标记;
  • 多个线程并行清除所有未标记的对象(死亡对象);
  • 多个线程并行将存活对象整理在一起,并将所有被回收对象的空间整合为一体。

优缺点:多个线程同时进行垃圾回收可以缩短应用的暂停时间(STW时间短),但是由于老年代的空间一般较大,所以在扫描和标记存活对象上需要花费较长时间。

2.3 并行GC(Concurrent Mark-Sweep GC、CMS GC)

操作步骤

  • 初始标记(STW Initial Mark):虚拟机暂停正在执行的任务(STW阶段),通过可达分析算法扫描出所有存活对象,并做出标记。此过程只会导致短暂的JVM暂停;
  • 并发标记(Concurrent Marking):恢复所有暂停的线程对象,并对之前标记过的对象进行扫描,取得所有跟标记对象关联的对象。
  • 并打预清理(Concurrent Precleaning):查找 所有在并发标记阶段新进入老年代的对象(一些对象可能刚从新生代晋升到老年代,或者有一些大对象被分配到老年代),通过重新扫描,减少下一阶段的工作;
  • 重新标记(STW Remark):此阶段会暂停虚拟机,对在“并发标记”被改变引用或新创建的对象进行标记;
  • 并发清理(Concurrent Sweeping):恢复所有暂停的应用线程,对所有未标记的垃圾对象进行清理,并且会尽量将已回收对象的空间重新拼凑为一个整体。在此阶段收集器线程和应用线程并发执行。
  • 并发重置(Concurrent Reset):重置CMS收集器的数据结构,等待下一次垃圾回收。

优缺点:只有在第一次和重新标记阶段才会暂停整个应用(STW),这样对应用程序带来的 影响非常的小。缺点是并发标记与回收线程会与应用线程争夺CPU资源,并且容易产生内存碎片。
GC14.png

Java虚拟机需要在运行的时候考虑其所处的环境,所以对于单机版的GC和服务器版的GC也是有所区别的。

No.运行环境年轻代GC老年代GC
1单机程序 (Client)串行GC(Serial Copying)串行GC(Serial MSC)
2服务器程序(Server)并行回收GC(Parallel Scavenge)并行GC(Parallel Mark Sweep、Parallel Compacting)

不同的JDK版本默认支持的GC回收算法是不通的,如果要修改GC的算法,可以自己手动利用JVM参数来进行配置。

No.参数名称年轻代效果老年代效果
1-XX:+UseSerialGC串行GC(Serial Copying)串行GC(Serial MSC)
2-XX:+UseParallelGC并行回收GC(Parallel Scavenge)并行GC(Parallel Mark Sweep、Parallel Compacting)
3-XX:+UseConcMarkSweepGC并行GC(ParNew)并行GC(Concurrent Mark-Sweep GC、CMS GC),当出现concurrent Mode Failure时采用串行GC(Serial MSC)
4-XX:+UseParNewGC并行GC(ParNew)串行GC(Serial MSC)
5-XX:+UseParallelOldGC并行回收GC(Parallel Scavenge)并行GC(Parallel Mark Sweep、Parallel Compacting)

程序示例

设置一个并行的GC处理操作

package JVM;
public class TestGC {
    public static void main(String[] args) {
        String str = new String("Hello RsThe");
        for(int x = 0; x < 713; x++){
            // 该操作会产生大量垃圾
            str += x;
            str.intern();
        }
    }
}

此代码中会产生大量的垃圾,默认的算法新版本之中不再使用串行或者是并行了

启动参数

-Xms2m -Xmx2m -Xss1m -XX:+UseSerialGC -Xlog:gc*

执行结果

[0.011s][info][gc] Using Serial(使用串行GC)
[0.011s][info][gc,heap,coops] Heap address: 0x00000000ffe00000, size: 2 MB, Compressed Oops mode: 32-bit
[0.043s][info][gc,start     ] GC(0) Pause Young (Allocation Failure 内存分配失败)(暂停年轻代)
[0.046s][info][gc,heap      ] GC(0) DefNew: 512K->64K(576K)
[0.046s][info][gc,heap      ] GC(0) Tenured: 0K->406K(1408K)
[0.046s][info][gc,metaspace ] GC(0) Metaspace: 95K->95K(1056768K)
[0.046s][info][gc           ] GC(0) Pause Young (Allocation Failure) 0M->0M(1M) 2.994ms
[0.046s][info][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s
[0.086s][info][gc,start     ] GC(1) Pause Young (Allocation Failure)
[0.087s][info][gc,heap      ] GC(1) DefNew: 576K->64K(576K)
[0.087s][info][gc,heap      ] GC(1) Tenured: 406K->641K(1408K)
[0.087s][info][gc,metaspace ] GC(1) Metaspace: 361K->361K(1056768K)
[0.087s][info][gc           ] GC(1) Pause Young (Allocation Failure) 0M->0M(1M) 1.222ms
[0.087s][info][gc,cpu       ] GC(1) User=0.00s Sys=0.00s Real=0.00s
[0.102s][info][gc,start     ] GC(2) Pause Young (Allocation Failure)
[0.104s][info][gc,heap      ] GC(2) DefNew: 576K->64K(576K)
[0.104s][info][gc,heap      ] GC(2) Tenured: 641K->788K(1408K)
[0.104s][info][gc,metaspace ] GC(2) Metaspace: 422K->422K(1056768K)
[0.104s][info][gc           ] GC(2) Pause Young (Allocation Failure) 1M->0M(1M) 1.217ms
[0.104s][info][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s
[0.138s][info][gc,start     ] GC(3) Pause Young (Allocation Failure)
[0.139s][info][gc,heap      ] GC(3) DefNew: 576K->64K(576K)
[0.139s][info][gc,heap      ] GC(3) Tenured: 788K->994K(1408K)
[0.139s][info][gc,metaspace ] GC(3) Metaspace: 960K->960K(1056768K)
[0.139s][info][gc           ] GC(3) Pause Young (Allocation Failure) 1M->1M(1M) 1.183ms
[0.139s][info][gc,cpu       ] GC(3) User=0.00s Sys=0.00s Real=0.00s
[0.145s][info][gc,start     ] GC(4) Pause Young (Allocation Failure)
[0.145s][info][gc           ] GC(4) Pause Young (Allocation Failure) 1M->1M(1M) 0.066ms
[0.145s][info][gc,cpu       ] GC(4) User=0.00s Sys=0.00s Real=0.00s
[0.145s][info][gc,start     ] GC(5) Pause Full (Allocation Failure)
[0.145s][info][gc,phases,start] GC(5) Phase 1: Mark live objects(标记存活对象)
[0.146s][info][gc,phases      ] GC(5) Phase 1: Mark live objects 1.134ms(标记耗费时间)
[0.146s][info][gc,phases,start] GC(5) Phase 2: Compute new object addresses(计算新对象的地址)
[0.147s][info][gc,phases      ] GC(5) Phase 2: Compute new object addresses 0.301ms(计算新对象地址耗费时间)
[0.147s][info][gc,phases,start] GC(5) Phase 3: Adjust pointers
[0.147s][info][gc,phases      ] GC(5) Phase 3: Adjust pointers 0.629ms
[0.147s][info][gc,phases,start] GC(5) Phase 4: Move objects(对象移动,碎片整理)
[0.147s][info][gc,phases      ] GC(5) Phase 4: Move objects 0.057ms
[0.148s][info][gc,heap        ] GC(5) DefNew: 574K->0K(576K)
[0.148s][info][gc,heap        ] GC(5) Tenured: 994K->1089K(1408K)
[0.148s][info][gc,metaspace   ] GC(5) Metaspace: 1036K->1036K(1056768K)
[0.148s][info][gc             ] GC(5) Pause Full (Allocation Failure) 1M->1M(1M) 2.346ms
[0.148s][info][gc,cpu         ] GC(5) User=0.00s Sys=0.00s Real=0.00s
[0.150s][info][gc,heap,exit   ] Heap
[0.150s][info][gc,heap,exit   ]  def new generation   total 576K, used 353K [0x00000000ffe00000, 0x00000000ffea0000, 0x00000000ffea0000)
[0.150s][info][gc,heap,exit   ]   eden space 512K,  68% used [0x00000000ffe00000, 0x00000000ffe58470, 0x00000000ffe80000)
[0.150s][info][gc,heap,exit   ]   from space 64K,   0% used [0x00000000ffe80000, 0x00000000ffe80000, 0x00000000ffe90000)
[0.150s][info][gc,heap,exit   ]   to   space 64K,   0% used [0x00000000ffe90000, 0x00000000ffe90000, 0x00000000ffea0000)
[0.150s][info][gc,heap,exit   ]  tenured generation   total 1408K, used 1089K [0x00000000ffea0000, 0x0000000100000000, 0x0000000100000000)
[0.150s][info][gc,heap,exit   ]    the space 1408K,  77% used [0x00000000ffea0000, 0x00000000fffb0710, 0x00000000fffb0800, 0x0000000100000000)
[0.150s][info][gc,heap,exit   ]  Metaspace       used 1039K, capacity 4573K, committed 4864K, reserved 1056768K
[0.150s][info][gc,heap,exit   ]   class space    used 102K, capacity 416K, committed 512K, reserved 1048576K

使用并行GC,执行参数

-Xms2m -Xmx2m -Xss1m -XX:+UseParallelGC -Xlog:gc*

执行结果

[0.013s][info][gc] Using Parallel(使用并行GC)
[0.013s][info][gc,heap,coops] Heap address: 0x00000000ffe00000, size: 2 MB, Compressed Oops mode: 32-bit
[0.046s][info][gc,start     ] GC(0) Pause Young (Allocation Failure 内存分配失败)(暂停年轻代)
[0.049s][info][gc,heap      ] GC(0) PSYoungGen: 512K->504K(1024K)(并行年轻代处理)
[0.049s][info][gc,heap      ] GC(0) ParOldGen: 0K->0K(512K)(并老年代处理)
[0.049s][info][gc,metaspace ] GC(0) Metaspace: 95K->95K(1056768K)
[0.049s][info][gc           ] GC(0) Pause Young (Allocation Failure) 0M->0M(1M) 2.355ms
[0.049s][info][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s
[0.087s][info][gc,start     ] GC(1) Pause Young (Allocation Failure)
[0.088s][info][gc,heap      ] GC(1) PSYoungGen: 1016K->512K(1024K)
[0.088s][info][gc,heap      ] GC(1) ParOldGen: 0K->216K(512K)
[0.088s][info][gc,metaspace ] GC(1) Metaspace: 360K->360K(1056768K)
[0.088s][info][gc           ] GC(1) Pause Young (Allocation Failure) 0M->0M(1M) 1.048ms
[0.088s][info][gc,cpu       ] GC(1) User=0.00s Sys=0.00s Real=0.00s
[0.105s][info][gc,start     ] GC(2) Pause Young (Allocation Failure)
[0.106s][info][gc,heap      ] GC(2) PSYoungGen: 1024K->512K(1024K)
[0.106s][info][gc,heap      ] GC(2) ParOldGen: 216K->352K(512K)
[0.106s][info][gc,metaspace ] GC(2) Metaspace: 439K->439K(1056768K)
[0.106s][info][gc           ] GC(2) Pause Young (Allocation Failure) 1M->0M(1M) 1.153ms
[0.106s][info][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s
[0.106s][info][gc,start     ] GC(3) Pause Full (Ergonomics)
[0.106s][info][gc,phases,start] GC(3) Marking Phase(标记阶段)
[0.107s][info][gc,phases      ] GC(3) Marking Phase 1.059ms
[0.107s][info][gc,phases,start] GC(3) Summary Phase(统计阶段)
[0.107s][info][gc,phases      ] GC(3) Summary Phase 0.007ms
[0.107s][info][gc,phases,start] GC(3) Adjust Roots(调整阶段)
[0.108s][info][gc,phases      ] GC(3) Adjust Roots 0.575ms
[0.108s][info][gc,phases,start] GC(3) Compaction Phase(压缩阶段)
[0.111s][info][gc,phases      ] GC(3) Compaction Phase 3.068ms
[0.111s][info][gc,phases,start] GC(3) Post Compact
[0.111s][info][gc,phases      ] GC(3) Post Compact 0.091ms
[0.111s][info][gc,heap        ] GC(3) PSYoungGen: 512K->510K(1024K)
[0.111s][info][gc,heap        ] GC(3) ParOldGen: 352K->331K(512K)
[0.111s][info][gc,metaspace   ] GC(3) Metaspace: 439K->439K(1056768K)
[0.111s][info][gc             ] GC(3) Pause Full (Ergonomics) 0M->0M(1M) 
... ...

所有的GC操作都和预期的操作形式非常类似的,,但是所有的GC都会出现STW暂停问题,只不过好的算法可以让STE的时间缩短。