深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

1.垃圾回收算法

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

1.1 标记-清除算法

  • 标记-清除算法是现代垃圾回收算法的思想基础。
  • 标记-清除算法将垃圾回收分为两个阶段:
    • 标记阶段:首先通过根节点,标记所有从根节点开始可达的对象,未被标记的对象就是未被引用的垃圾对象
    • 清除阶段:清除所有未被标记的对象
  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • 此种方法的两个问题:
    • 效率问题
      • 标记和清除两个过程的效率都不高
    • 空间问题:
      • 标记清除之后会产生大量不连续的内存碎片,空间碎片太多会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

1.2 复制s

 

 

 

2.垃圾收集器

2.1 概述

  • 问题1:垃圾回收算法和垃圾回收器有什么关系?
  • 垃圾回收算法是垃圾回收的方法论,垃圾收集器是垃圾回收算法的具体实现
  • 问题2:为什么有这么多种垃圾回收器?
  • Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,
  • 因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都有可能有很大差别
  • 目前为止,还没有完美的收集器出现,Java的应用场景很多,没有万能的收集器能解决所有应用场景,只是针对具体应用选择最合适的收集器,进行分代收集
  •  

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

2.2 七大垃圾收集器

代码后结果的参数提前说明:

  • DefNew:Defalut New Generation,默认新生代
  • Tenured:Old,老年代
  • ParNew:Parallel New Generation,在新生代用并行回收
  • PSYoungGen:Parallel Scavenge
  • ParOldGen:Parallel Old Generation,在老年代用并行回收

JVM中Server/Client分别是什么意思:

  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • 适用范围:只需要掌握Server模式,Client基本不会用
  • 操作系统:
    • 32位Window操作系统,不论硬件如何都默认使用Client的JVM模式
    • 32位其他操作系统,2G内存同时有2个CPU以上使用的是Server模式,低于该配置还是Client模式
    • 64位的操作系统都是Server模式

2.2.1 新生代垃圾收集器

(1)Serial收集器(新生代串行GC/Servial Copying)

  • 它是最早使用的一个收集器,在JDK1.3.1之前是唯一的选择
  • 一个单线程的收集器:即在垃圾回收的时候只会使用一个CPU或一个收集线程完成垃圾回收工作
  • 在进行垃圾收集的时候,必须暂停其他所有的工作线程直到它收集结束
  • 如图:
  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • 一对一:新生代和老年代都为单线程
  • 说明:STW即为Stop The World,即暂停所有应用程序线程

优点:

  • 简单而高效
  • 对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率

缺点:

  • 收集期间需要暂停所有应用线程,用户体验不好

应用场景:

  • Java虚拟机运行在Client模式下默认的新生代垃圾收集器

对应JVM参数是:-XX:+UseSerialGC

  • 当我们使用此参数开启Serial,老年代默认会开启Serial Old
    • 即开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合
    • 表示新生代和老年代都会使用串行回收收集器
  • 新生代使用复制算法,老年代使用标记-整理算法

示例代码演示:

import java.util.Random;

public class GCDemo {
    public static void main(String[] args) {
        System.out.println("=====GCDemo,Hello====");
        try {
            String str = "GCDemo";
            while (true){
                str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
                str.intern();
            }
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

配置JVM参数:深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

结论:DefNew+Tenured

(2)ParNew(新生代并行GC)

  • 使用多线程进行垃圾回收
  • 在进行垃圾收集的时候,必须暂停其他所有的工作线程直到它收集结束
  • 如图:
  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • ParNew收集器其实是Serial收集器新生代的并行多线程版本

应用场景:

  • 最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样
  • 它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器

对应JVM参数是:-XX:+UseParNewGC

  • 启用ParNewGC收集器,只影响新生代的收集,不影响老年代
  • 开启后会使用:ParNew(Young区用)+Serial Old(Old区用)的收集器组合
  • 新生代使用复制算法,老年代使用标记-整理算法

备注:

  • -XX:ParallelGCThreads    限制线程数量,默认开启和CPU数目相同的线程数

示例代码演示:

import java.util.Random;

public class GCDemo {
    public static void main(String[] args) {
        System.out.println("=====GCDemo,Hello====");
        try {
            String str = "GCDemo";
            while (true){
                str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
                str.intern();
            }
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

配置JVM参数:深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

结论:ParNew+Tenured,但是这种组合已经不再推荐被使用

(3)Parallel(并行回收GC/Parallel Scavenge)

  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器
  • 使用复制算法
  • 也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器
  • 多对多:新生代和老年代均为多线程

有了ParNew为什么还要Parallel?

  • Parallel重点关注的是:可控制的吞吐量
    • 吞吐量=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)
    • 即比如程序运行100分钟,垃圾收集时间位1分钟,吞吐量为99%
    • 高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务
      • 比如你在前台下个单,它停顿来算,交互性差,而对于科学计算,它自己在后台计算,停顿一会我们也不知道,但是它高效利用了CPU
  • 自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别
    • 自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量

对应JVM参数是:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)

备注:

  • -XX:ParallelGCThreads=数字N    表示启动多少个GC线程
  • cpu>8:  N=5/8
  • cpu<8:  N=实际个数

示例代码演示:

import java.util.Random;

public class GCDemo {
    public static void main(String[] args) {
        System.out.println("=====GCDemo,Hello====");
        try {
            String str = "GCDemo";
            while (true){
                str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
                str.intern();
            }
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

配置JVM参数:

  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

结论:PSYoungGen+ParOldGen

2.2.2 老年代垃圾收集器

(1)Parallel Old收集器

  • Parallel Old收集器是Parallel Scavenge的老年代版本
  • 使用多线程的标记-整理算法
  • Parallel Old收集器在JDK1.6才开始提供
    • 在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量
  • Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略

对应JVM参数是:-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活,即配置其中一个,另一个会自动连带激活)

示例代码演示1:

import java.util.Random;

public class GCDemo {
    public static void main(String[] args) {
        System.out.println("=====GCDemo,Hello====");
        try {
            String str = "GCDemo";
            while (true){
                str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
                str.intern();
            }
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

配置JVM参数:

  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

 

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

结论:PSYoungGen+ParOldGen

示例代码演示2:

import java.util.Random;

public class GCDemo {
    public static void main(String[] args) {
        System.out.println("=====GCDemo,Hello====");
        try {
            String str = "GCDemo";
            while (true){
                str += str + new Random().nextInt(77777777) + new Random().nextInt(88888888);
                str.intern();
            }
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

配置JVM参数:​​​​​​​

  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器
  • 不配置垃圾回收器的任何参数

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

 

结论:系统默认是PSYoungGen+ParOldGen

(2)CMS收集器(并发标记清除GC)

  • CMS收集器(Concurrent Mark Sweep:并发标记清除)
  • 是一种以获取最短回收停顿时间为目标的收集器
  • 深入理解JVM(3)——GC垃圾回收(2)——4大垃圾回收算法和7大垃圾收集器

应用场景:

  • 适合应用在互联网站或者B/S系统的服务器上
  • 这类应用尤其重视服务器的响应速度,希望系统停顿时间最短
  • CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器

 

 

 

 

 

 

 

 

上一篇:Parallel.ForEach 使用多线遍历循环


下一篇:jvm默认垃圾收集器(JDK789)