—-慢慢来比较快,虚心学技术—-

常用垃圾回收算法

一、标记-清除算法

算法执行步骤

1、标记:遍历内存区域,标记所有需要回收的对象(结合可达性分析算法对无用对象进行标记)
2、清除:遍历内存区域,统一回收所有被标记的对象内存

图片.png

缺点
1、两次遍历,效率过低
2、容易产生大量内存碎片,当再需要一块较大的内存时,无法找到一块满足要求的,因而不得不再次触发GC

二、复制算法

算法描述:将内存空间分为等大的两块(对象块&空白块),每次只使用其中一块,当一块用完之后,触发GC时,将该块中尚且存活的对象复制到另一块区域,然后一次性清理掉这块没用的内存,下次则对调过来执行,如此往复。

优点:解决了标记清除算法的内存碎片问题,且清除对象效率更高(一波带走)

缺点
1、内存利用率不高,每次都只能使用一半的内存
2、当内存中对象存活率较高时,重复执行复制操作就会显得耗时且无意义

三、标记-整理算法

算法步骤
1、标记:遍历内存区域,标记所有需要回收的对象(结合可达性分析算法对无用对象进行标记)
2、整理:清除所有标记对象,同时让存活对象往内存一端移动

图片.png

优点:解决了内存碎片问题,也提高了内存利用率

缺点**:既有标记清除,又有移动过程,效率更低

四、分代回收算法(主角)

算法描述是目前大部分JVM的垃圾收集器采用的算法。并不是一种全新的算法,而是根据对象存活时间长短按需整合了前述三种算法。
**
如前面文章所述,目前堆内结构分为年轻代和老年代(比例默认:1:2),而年轻代中又分为两个区:生成区(Eden)和幸存区(Survivor),幸存区也分为两个区:From Space(S0)和To Space(S1),所以年轻代中内存分布是Eden:S0:S1=8:1:1

图片.png

年轻代垃圾回收(Minor GC)
**
1、大部分新生成的对象都会先放在Eden区,回收时先将Eden区存活对象复制到Survivor其中一个区From区,然后清空Eden区

2、后续回收时,将From区和Eden区的存活对象复制到另一个Survivor区To区,然后清空Eden区和From区。

3、部分对象会在From区域和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还存活,就存入老年代

特点**

年轻代的GC别称为Minor GC,实际上,更像是改良版的复制算法
Minor GC 的执行频率较高,并不一定等到Eden区满才执行,速度极快
基本每次都会有对象死亡,回收量很大

老年代垃圾回收(Major GC/Full GC)
**
如上所述,对象在年轻代经过N轮垃圾回收后仍存活的对象,就会被存放到老年代,可以认为,老年代中存放的都是一些生命周期较长的对象

当老年区内存满了之后,就会触发Major GC也称Full GC,回收整个内存空间的死亡对象内存

特点**:**

执行速度较慢,相较于Minor GC速度慢了10倍左右
执行频率

图片.png

实例

好了,上面讲了一大堆的概念,我们最后用实例来看一下实时垃圾回收的表现:

现有类Test和HeapTest,代码如下:

  1. public class Test {
  2. public static void main(String[] args) throws InterruptedException {
  3. List<HeapTest> heapTests = new ArrayList<>();
  4. while (true){
  5. heapTests.add(new HeapTest());
  6. Thread.sleep(5);
  7. }
  8. }
  9. }
  10. class HeapTest {
  11. private HeapTest heapTest;
  12. public void setHeapTest(HeapTest heapTest){
  13. this.heapTest = heapTest;
  14. }
  15. }

运行程序,打开docs窗口,执行jvisualvm命令调用jvm监控工具

图片.png

自动调用Java VisualVM视图窗口(VisualVM是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序
图片.png

其视图窗口包含本地及远程的程序监控,可以查看每个线程的概述,监视CPU、类、线程、内存等信息,而我们需要关注的重点是Visual GC面板,该面板展示的是程序运行中的堆内存及元空间变化,进而观察程序运行期间的回收动作

从Graphs面板可以看到各个空间分区和GC的实时变化,Spaces面板可以看到各个空间分区的内存状况。

从面板可以看到熟悉的Eden(生成区),Survivor 0(幸存0区) 和Survivor 1(幸存1区)以及Old(老年区)

现有一个测试类如下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        List<HeapTest> heapTests = new ArrayList<>();
        while (true){
            heapTests.add(new HeapTest());
            Thread.sleep(5);
        }
    }
}

①当线程运行起来后,可以看到Eden区的内存逐渐增大,达到内存巅峰的时候进行了一次内存GC,将所有Eden区中尚且存活的对象复制到S1区,同时将大对象直接放入Old区,然后将Eden区清空

②第二次Eden区内存满了的时候,将Eden区和S1区所有存活的对象复制到S0区,同时清空Eden区和S1区内存。

③第三次Eden区内存满了的时候,将Eden区和S0区所有存活的对象复制到S1区,同时清空Eden区和S0区内存

④每次GC都会对存活对象进行标识,如果标识次数即经历回收达到一定次数,则认为该对象是生命周期较长的对象,选择将该对象存入Old区

图片.png

如此往复,直到Old区再也装不下对象时会执行一次Full GC,如果执行完Full GC 后仍旧没有足够内存满足需求,那么就会抛出异常OutOfMemoryError(OOM),程序停止运行

图片.png
注**
1、什么时候会执行GC?**

年轻代GC发生在新创建的对象在Eden区无法申请到空间(即Eden区满)的时候
老年代GC发生在Old区空间已满或调用System.gc()方法时

2、没有Visual GC面板?

工具->插件->可用插件->Visual GC->安装

图片.png

图片.png

常见的垃圾收集器

图片.png

年轻代垃圾收集器

Serial收集器(串行收集器)

图片.png
线程单线程收集器(单线程仅有一条垃圾回收线程,而且在垃圾回收的线程执行时,会暂停用户线程,直至回收结束,Stop The World)

算法**:复制算法

显式配置-XX:+UseSerialGC

应用场景:Client模式

优点**:简单高效(单CPU环境下,无需与其他线程交互,专心GC)

缺点**:造成应用停顿时间

ParNew收集器

图片.png

特点**:多线程并行(用户线程等待)

线程多线程收集器(是Serial收集器的多线程版本,除了线程数,其余特征一致,和Serial收集器共用了不少代码,Stop The World)

算法**:复制算法

显式配置-XX:+UseParNewGC**(指定使用ParNew收集器)
**-XX:+UseConcMarkSweepGC**(指定使用CMS收集器,会默认使用ParNew收集器作为年轻代收集器)
**-XX:ParallelGCThreads**(指定垃圾收集线程数量)

应用场景:Server模式

优点**回收速度更快

缺点**:**在单CPU环境下,增加了线程交互开销

Parallel Scavenge收集器(吞吐量优先收集器)

图片.png

特点**器如其名,专注于吞吐量优化,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

线程多线程并发

算法**:复制算法

显式配置-XX:+UseParallelGC **(显示指定使用Parallel Scavenge收集器)
**-XX:GCTimeRatio**(控制吞吐量大小)
**-XX:MaxGCPauseMills** (最大垃圾收集时间)

应用场景:不过分关注暂停时间的应用(如后台定时任务,科学计算器等)

优点**:**吞吐量得到保证

老年代垃圾收集器

Serial Old收集器(串行收集器)

特点Serial收集器的老年代版本

线程单线程收集器

算法**:标记-整理算法

显式配置****-XX:+UseSerialGC

应用场景:Client模式

优点**简单高效(单CPU环境下,无需与其他线程交互,专心GC)

缺点**:**造成应用停顿时间

Parallel Old收集器(吞吐量优先收集器)

特点**:Parallel Scavenge收集器的老年代版本,JDK1.6后才有

线程多线程收集器

算法**:标记-整理算法

显式配置-XX:+UseParallelOldGC **(显示指定使用Parallel Old收集器)

应用场景:Server模式

优点**:**吞吐量得到保证

CMS(Concurrent Mark Sweep)收集器

图片.png

特点**:专注于获取最短回收停顿时间,非常适合用于注重用户体验的应用,第一次实现了让垃圾收集线程与用户线程同时工作

线程多线程收集器(此处的多线程是并发的,造成的用户停顿非常短暂,甚至存在与用户线程同步进行的情况)

算法**:标记-清除算法

执行步骤**:

1、初始标记:标记GC Roots直接关联的对象(仅运行收集线程,Stop The World),速度较快

2、**并发标记**:进行GC Roots Tracing的过程,标记出”所有上一步骤被标记的对象”所能到达的对象(时长较长,此进程与用户线程并发进行,不会造成停顿)

3、重新标记:更改在并发标记的过程中,用户线程并发运行时产生变动的标记对象的标记(仅运行收集线程,Stop The World)

4、**并发清除**:清除之前未被标记的所有垃圾(与用户进程并发进行,不会造成停顿)

显式配置**-XX:+UseConcMarkSweepGC (显示指定使用CMS收集器)

应用场景:与用户交互较多的应用场景,如Web网站等

优点**:用户体验较好,系统停顿极短

缺点**:
必然存在大量内存碎片,需要更多的内存
无法解决最后并发清除阶段用户线程产生新垃圾且没清除的问题

独立回收

Garbage-First收集器(G1收集器)

特点**一个并行、并发和增量式压缩低停顿**的垃圾收集器,为了替代CMS收集器,解决了CMS收集器的内存碎片和内存空间需求等问题

传统的垃圾收集器将堆内存分为年轻代(Eden区和Survivor区)和老年代,而G1将堆内存分为多个大小相同的区域(**Region**),每个Region拥有自己的分代属性,但是分代不需要连续

图片.png

由于分代不连续,可以有效解决内存碎片化的问题,但是也会导致每次GC的时候都需要全盘扫描找出引用内存,所以G1收集器为每个Region都维护了一个Remenbered Set,用于记录对象引用的情况(是不是很像索引?),当发生GC的时候,从Remenbered Set出发搜索,有效降低耗时

GC模式**:G1收集器仅有两种GC模式,Full GC交给Serial Old收集器执行

YoungGC(年轻代收集)
**
当所有Eden Region使用达到阈值,且无法申请足够内存时,会触发一次YoungGC
每次YoungGC会回收所有Eden区和Survivor区(From),且将存活对象复制到Old区和另一个Survivor区(复制算法)

MixedGC(混合收集)

MixedGC是G1收集器独有的GC模式

我们知道,每次YoungGC都会将Eden区和Survivor区(From)的存活对象都复制到另一个Survivor区(To),然后回收Eden区和From区内存;

那么,MixedGC就是将一部分老年区(Old区)加到将被清理的Eden区和Survivor区(From)的后面,合称collection set,就是即将被回收的集合,等下次MixedGC将该集合的区域一并清理。选择Old区域优先选择E垃圾较多即存活对象较少的区域(所以称为Garbage-First)

线程多线程收集器(此处的多线程是并发的,造成的用户停顿非常短暂,甚至存在与用户线程同步进行的情况)

算法**:整体使用“标记-整理”算法,局部使用“复制算法”

执行步骤**:

图片.png

1、初始标记:标记GC Roots直接关联的对象(仅运行收集线程,Stop The World),速度较快

2、并发标记:进行GC Roots Tracing的过程,标记出”所有上一步骤被标记的对象”所能到达的对象(时长较长,此进程与用户线程并发进行,不会造成停顿)

3、最终标记:更改在并发标记的过程中,用户线程并发运行时产生变动的标记对象的标记(仅运行收集线程,Stop The World)

4、筛选回收:统计及清除Remenbered Set,同时识别完全空白的区域以及可供混合垃圾回收的区域,选择GC模式进行垃圾回收(仅运行收集线程,Stop The World)

显式配置**-XX:+UseG1GC(显示指定使用G1收集器)

应用场景:与用户交互较多的应用场景,如Web网站等

优点**:用户体验较好,系统停顿极短

垃圾收集器回顾


Serial ParNew Parallel Scavenge Serial Old Parallel Old CMS G1
是否并行 串行 并行 并行 串行 并行 并行 并行
是否并发 并发 并发 并发
作用代 年轻代 年轻代 年轻代 老年代 老年代 老年代 年轻代和老年代

总结

1、常用垃圾回收算法包括:标记-清除算法、复制算法、标记-整理算法、分代回收算法等,每个算法都在不断的完善上一算法的缺陷,提高回收效率

2、常见的垃圾收集器包括Serial收集器、ParNew收集器、Parallel Scavenge收集器、Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器等,每个收集器的线程并行并发和作用代都不尽相同。

如有贻误,还请评论指正