垃圾收集器是垃圾回收算法(标记-清除算法、复制算法、标记-整理算法、火车算法)的具体实现,不同商家、不同版本的JVM所提供的垃圾收集器可能会有很在差别,本文主要介绍HotSpot虚拟机中的垃圾收集器。

下图是java8 HotSpot虚拟机所有的垃圾收集器,连接先代表可也配合使用的组合,G1是对整个堆进行收集

Java的垃圾收集器_垃圾回收

 

用于新生代的收集器有:Serial、ParNew、Paraller Scavenge

用于老年代的收集器有:CMS(Concurrent Mark Sweep)、Serial Old、Parallel Old

G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。

收集器的分类

 

 

  • 串行垃圾收集器:Serial、Serial Old
  • 并行垃圾收集器:ParNew、Parallel Old、Paraller Scavenge
  • 并发垃圾收集器:CMS(Concurrent Mark Sweep)
  • G1:G1的话比较特殊一点,接下来进行介绍

如何查看当前java默认使用的是哪种垃圾收集器?

java -XX:+PrintCommandLineFlags -version

Java的垃圾收集器_老年代_02

 

 

 UseParallelGC代表新生代默认使用的是Paraller Scavenge进行垃圾收集的,而老年代则使用是Parallel Old进行垃圾收集的

当然也可以使用IDEA进行测试,准备测试代码,目的是查看GC收集的信息

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;

public class GCTest {
    public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<Object>(); while(true)

        {
            int sleep = new Random().nextInt(100);
            if (System.currentTimeMillis() % 2 == 0) {
                list.clear();
            } else {
                for (int i = 0; i < 10000; i++) {
                    Properties properties = new Properties();
                    properties.put("key_" + i, "value_" + System.currentTimeMillis() + i);
                    list.add(properties);
                }
            }
            Thread.sleep(sleep);
        }

    }
}

在程序启动之前设置VM options属性

Java的垃圾收集器_垃圾收集_03

 

-XX:+PrintGCDetails -Xms16m -Xmx16m

 启动测试,观察控制台

Java的垃圾收集器_老年代_04

 

 

 新生代和老年的所使用的垃圾收集器如上图

串行垃圾收集器

串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)。
对于交互性较强的应用而言,这种垃圾收集器是不能够接受的。一般在Javaweb应用中是不会采用该收集器的。
设置为串行垃圾收集器
-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m

控制台打印信息

Java的垃圾收集器_垃圾收集_05

 

 可以看到垃圾收集已经变为DefNew和Tenured的的组合进行了,也就是Serial和Serial Old的组合

Java的垃圾收集器_垃圾收集器_06

 

 并行垃圾收集器

也就是JAVA8中默认的垃圾收集器,但是还有一个新生代的选择,那就是Par New,

ParNew 和Parallel Scavenge的不同之处

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

Java的垃圾收集器_垃圾回收_07

 

 

 

设置为ParNew进行新生代的垃圾回收

-XX:+UseParNewGC -XX:+PrintGCDetails -Xms16m -Xmx16m

Java的垃圾收集器_垃圾收集器_08

通过-XX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器
ParallelGC收集器
ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。
相关参数如下:
  -XX:+UseParallelGC
    年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。
  -XX:+UseParallelOldGC
    年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
  -XX:MaxGCPauseMillis
    设置最大的垃圾收集时的停顿时间,单位为毫秒需要注意的时,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。该参数使用需谨慎。
  -XX:GCTimeRatio
    设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%
  -XX:UseAdaptiveSizePolicy
    自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、堆大小、停顿时间之间的平衡。一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。

并发垃圾收集器

CMS垃圾收集器

CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。
CMS垃圾回收器的执行过程如下: 
Java的垃圾收集器_垃圾收集器_09
  1. 初始化标记(CMS-initial-mark) ,标记root,会导致stw;
  2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;
  3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
  4. 重新标记(CMS-remark) ,会导致stw;
  5. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  6. 调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;
  7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;

Java的垃圾收集器_老年代_10

 

 

 

设置以CMS方式进行垃圾收集

-XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms16m -Xmx16m

 

Java的垃圾收集器_垃圾回收_11

G1垃圾收集器

G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。

Java的垃圾收集器_垃圾收集_12

 

 

 原理

  G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。
  这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。
Java的垃圾收集器_垃圾收集_13

 

 

  在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。
这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。在G1中,有一种特殊的区域,叫Humongous区域。
  • 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。
  • 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
  • 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

Young GC

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。
  • Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。
  • Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

Java的垃圾收集器_垃圾收集_14

 

 Java的垃圾收集器_垃圾收集_15

 

 Rembered Set

 

在GC年轻代的对象时,我们如何找到年轻代中对象的根对象呢?
  根对象可能是在年轻代中,也可以在老年代中,那么老年代中的所有对象都是根么?如果全量扫描老年代,那么这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,其作用是跟踪指向某个堆内的对象引用。
  无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描
Java的垃圾收集器_老年代_16

 

 

每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是 xx Region的 xx Card。
 

Mixed GC 

  当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个YoungRegion,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。
  
  MixedGC什么时候触发?
由参数 -XX:InitiatingHeapOccupancyPercent=n 决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。
  它的GC步骤分2步
    1. 全局并发标记(global concurrent marking)
    2. 拷贝存活对象(evacuation)
全局并发标记
1.初始标记(initial mark,STW)
  标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。
2.根区域扫描(root region scan)
  G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
3.并发标记(Concurrent Marking)
  G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断。
4.重新标记(Remark,STW)
  该阶段是 STW 回收,因为程序在运行,针对上一次的标记进行修正。
5.清除垃圾(Cleanup,STW)
  清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收。
拷贝存活对象
Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。
设置以G1方式进行垃圾回收
-XX:+UseG1GC -XX:+PrintGCDetails -Xms16m -Xmx16m

young GC

Java的垃圾收集器_垃圾回收_17

 

 Mixed GC

Java的垃圾收集器_垃圾收集_18

 

 G1的特点

(1)并行与并发

      能充分利用多CPU、多核环境下的硬件优势;可以并行来缩短"Stop The World"停顿时间;也可以并发让垃圾收集与用户程序同时进行;

(2)分代收集,收集范围包括新生代和老年代    

      能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;能够采用不同方式处理不同时期的对象;      虽然保留分代概念,但Java堆的内存布局有很大差别;将整个堆划分为多个大小相等的独立区域(Region);新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;