JVM(七)--- 垃圾回收

目录

一、垃圾回收概述

[1. 什么是垃圾](#1. 什么是垃圾)

[2. Java垃圾回收机制](#2. Java垃圾回收机制)

二、垃圾回收相关算法

[1. 垃圾标记算法](#1. 垃圾标记算法)

[1.1 引用计数算法](#1.1 引用计数算法)

[1.2 可达性分析算法](#1.2 可达性分析算法)

[2. 对象的finalization机制](#2. 对象的finalization机制)

[3. 清除阶段](#3. 清除阶段)

[3.1 标记-清除算法(Mark-Sweep)](#3.1 标记-清除算法(Mark-Sweep))

[3.2 复制算法](#3.2 复制算法)

[3.3 标记-压缩算法](#3.3 标记-压缩算法)

[4. 分代收集算法](#4. 分代收集算法)

[5. 增量收集算法](#5. 增量收集算法)

[6. 分区算法](#6. 分区算法)

三、垃圾回收相关概念

[1. System.gc()](#1. System.gc())

[2. 内存溢出](#2. 内存溢出)

3.内存泄漏

[4. Stop The World](#4. Stop The World)

[5. 安全点与安全区域](#5. 安全点与安全区域)

[6. 四种引用](#6. 四种引用)

[6.1 软引用](#6.1 软引用)

[6.2 弱引用](#6.2 弱引用)

[6.3 虚引用](#6.3 虚引用)

四、垃圾回收器

[1. GC的分类和性能指标](#1. GC的分类和性能指标)

[1.1 分类](#1.1 分类)

[1.2 性能指标](#1.2 性能指标)

[2. 不同的垃圾回收器](#2. 不同的垃圾回收器)

[3. Serial回收器:串行回收](#3. Serial回收器:串行回收)

[4. ParNew回收器:并行回收](#4. ParNew回收器:并行回收)

[5. Parallel回收器:吞吐量优先](#5. Parallel回收器:吞吐量优先)

[6. CMS回收器:低延迟](#6. CMS回收器:低延迟)

[7. G1垃圾回收器:区域化分代](#7. G1垃圾回收器:区域化分代)

[7.1 优势与不足](#7.1 优势与不足)

[7.2 适用场景](#7.2 适用场景)

[7.3 Region的介绍](#7.3 Region的介绍)

[7.4 记忆集](#7.4 记忆集)

[7.5 G1回收器垃圾回收过程](#7.5 G1回收器垃圾回收过程)

[7.5.1 年轻代GC](#7.5.1 年轻代GC)

[7.5.2 并发标记过程](#7.5.2 并发标记过程)

[7.5.3 混合回收](#7.5.3 混合回收)

[8. 总结](#8. 总结)


一、垃圾回收概述

1. 什么是垃圾

垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。

2. Java垃圾回收机制

二、垃圾回收相关算法

1. 垃圾标记算法

1.1 引用计数算法

1.2 可达性分析算法

该算法可以有效解决在引用计数算法中循环引用的问题,防止内存泄露的发生。

"GC Roots"根集合就是一组必须活跃的引用。

如下图所示:

在Java语言中,GC Roots包括以下几类元素:

  • 虚拟机栈中引用的对象
  • 本地方法栈中引用的对象
  • 类静态属性引用的对象
  • 常量引用的对象(例如字符串常量池中的引用)
  • 所有被同步锁synchronized所持有的对象
  • Java虚拟机内部的引用

2. 对象的finalization机制

不要主动去调用某个对象的finalize()方法,应该交给垃圾回收机制调用。

由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态:

  1. 可触及的:从GC Roots根结点开始,可以到达的对象。
  2. 可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活。
  3. 不可触及的:对象的finalize()被调用,并且没有复活,那么就会进入不可触及状态。

只有对象在不可触及状态的时候才可以被回收。

如果想要第一次被标记的对象复活,就需要重写finalize()方法。

3. 清除阶段

当成功区分出内存中存活对象和死亡对象之后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的空间。

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

优点:简单、方便。

缺点:

  • 效率不算高
  • 在进行GC的时候需要停止整个应用程序
  • 这种方式清理出来的空闲空间内存是不连续的,会产生内存碎片。需要维护一个空闲列表

3.2 复制算法

如下图所示:

A和B是两块内存空间,要对A进行垃圾回收的话,就将标记的对象复制到内存B中,然后清空内存A。之后放数据往内存B中放,若B要进行垃圾回收,则再往A中复制。

优点:

  • 没有清除过程,实现简单,运行高效
  • 复制过去后保证空间的连续性,不会出现"碎片"问题

缺点:

  • 内存空间始终有一半是空闲状态
  • 存活对象如果过多,那么时间开销和资源开销就会很大

3.3 标记-压缩算法

优点:

  • 消除了标记-清除算法中内存分散的缺点,当需要给新对象分配内存的时候,JVM只需要持有一个内存的起始地址即可。
  • 消除了复制算法中内存减半的高额代价。

缺点:

  • 效率较低
  • 移动对象的同时,如果对象被引用,还需要修改引用的地址
  • 移动过程中,需要全程暂停用户应用程序。即:STW

4. 分代收集算法

分代收集算法并不是一种具体的算法,而是一种思想。

不同对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把Java堆分为新生代和老年代,这样就可以根据各个区域的特点使用不同的回收算法。

目前几乎所有的GC都是采用分代收集算法执行垃圾回收的。

5. 增量收集算法

为了解决STW状态,诞生了增量收集算法。

缺点:使用这种方法,虽然能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

6. 分区算法

如果设置的停顿时间短,那么就少回收几个region。如果停顿时间长,就多回收几个region。

三、垃圾回收相关概念

1. System.gc()

就是说,就算在程序中调用了System.gc()方法,该方法也不一定会调用垃圾回收器来对垃圾进行回收。

2. 内存溢出

内存溢出指的就是OOM异常,没有空闲内存,并且垃圾收集器也无法提供更多内存。

3.内存泄漏

严格来说,只有当对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄露。

尽管内存泄露不会立刻引起程序崩溃,但是一旦发生内存泄露,程序中的可用内存就会被逐步蚕食,直到耗尽所有内存,最终出现OOM异常。

例如创建了许多static的变量,生命周期和类一样长,即便之后不用了,也无法被回收,导致内存泄漏。

上图的例子:红框框出来的对象都不再使用了,但是一个忘记断开的引用也会导致内存泄漏。

4. Stop The World

5. 安全点与安全区域

程序在执行过程中并非在所有地方都能停下来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置称为"安全点"。

安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的。我们也可以把安全区域看作是被扩展的安全点。

6. 四种引用

6.1 软引用

软引用通常来实现内存敏感的缓存。例如高速缓存就用到软引用。第二次回收指的是在回收完那些不可达的对象之后内存还是不够,才进行第二次回收。

6.2 弱引用

弱引用也是用来描述那些非必须的对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

6.3 虚引用

虚引用不能单独使用,也无法通过虚引用来获取被引用的对象。当试图通过虚引用的get()方法获取对象时,总是null。

四、垃圾回收器

1. GC的分类和性能指标

1.1 分类

按线程数分,可以分为串行垃圾回收器并行垃圾回收器

串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾回收工作结束。

并行收集可以运用多个CPU同时执行垃圾回收,因此提升了吞吐量,不过并行回收和串行回收一样,采用独占式,使用了STW机制。

按照工作模式分,可以分为并发式垃圾回收独占式垃圾回收。

按碎片处理方式分,可以分为压缩式垃圾回收器非压缩式垃圾回收器。

按工作的内存空间分,可分为年轻代垃圾回收器老年代垃圾回收器

1.2 性能指标

2. 不同的垃圾回收器

7种经典的垃圾回收器:

一个新生代的垃圾回收器需要组合一个老年代的垃圾回收器,组合关系如下。红色虚线表示JDK8之前的关系,绿色虚线在JDK14时弃用了**。CMS垃圾回收器在JDK14也删除掉了**。

3. Serial回收器:串行回收

4. ParNew回收器:并行回收

ParNew收集器除了采用并行回收 的方式执行内存回收外,它与Serial回收器几乎没有任何区别。PerNew收集器在年轻代中同样也是采用复制算法、"Stop-the-World"机制。

ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器。

5. Parallel回收器:吞吐量优先

6. CMS回收器:低延迟

CMS采用标记-清除算法,第一次实现了让垃圾回收线程与用户线程同时进行工作。

  1. 初始标记阶段 :程序中所有的工作线程因为STW机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象。一旦标记完成之后就会恢复所有的线程。这里的速度非常快。
  2. 并发标记阶段 :从GC Roots的能直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程。
  3. 重新标记阶段 :由于在并发标记过程中,程序的工作线程和垃圾回收线程并发执行,因此为了修正并发标记期间,因为用户程序运行而导致标记产生变动的那一部分对象,这个阶段时间稍长,但也比第二阶段短的多。
  4. 并发清除阶段 :此阶段清理掉的标记阶段判断的已经死亡的对象,释放空间。

CMS优点:

  • 并发收集
  • 低延迟

CMS缺点:

  • 会产生内存碎片
  • CMS收集器对CPU资源非常敏感
  • CMS收集器无法处理浮动垃圾

7. G1垃圾回收器:区域化分代

G1是一个并行回收器,它把堆内存分割为很多不相关的区域。使用不同的Region来表示Eden、Survivor0、Survivor1和老年代。

G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表 ,每次根据允许的收集时间,优先回收价值最大的Region。

7.1 优势与不足

优势:

  • G1垃圾回收器兼顾并行与并发。
  • 将堆空间分为多个Region,既可以回收新生代,也可以回收老年代。
  • 空间整合:Region之间是复制算法,整体上看是标记-压缩算法。GC之后会将现有的资源放到一起,不会产生内存碎片。
  • 可预测的停顿时间模型:后台维护了一个优先列表,每次根据允许的收集时间,优先回收价值最高的最大的Region,保证G1收集器在有限时间内可以获取尽可能高的收集效率。

缺点:

7.2 适用场景

7.3 Region的介绍

7.4 记忆集

一个Region中的对象可能被其他任意Region中的对象所引用,判断对象是否存活,需要扫描整个Java堆才能保证准确。

无论是G1还是其他分代收集器,JVM都是使用Rset来避免全局扫描。即每一个Region都有一个对应的Rset,Rset中记录了有哪些区域的对象引用了该Region中的对象。

如下图所示:

7.5 G1回收器垃圾回收过程

G1的垃圾回收过程主要包括三个环节:年轻代GC、老年代并发标记过程、混合回收。

如果需要,Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。

7.5.1 年轻代GC
7.5.2 并发标记过程

首先标记GC Roots直接可达的对象 --> 标记survivor区中可以直接到老年代的对象 --> 对整个堆进行标记(并发) --> 再次标记 --> 计算各个区域的GC回收比例 --> 并发清理。

7.5.3 混合回收

第二阶段老年代Region中全是垃圾的被回收了,部分为垃圾的在第三阶段--混合回收的时候被回收。

8. 总结

相关推荐
艾菜籽4 小时前
JVM的类加载机制
jvm
小胖同学~6 小时前
JVM内存模型剖析
java·jvm
艾菜籽6 小时前
JVM中的内存区域划分
jvm
9毫米的幻想10 小时前
【Linux系统】—— 程序地址空间
java·linux·c语言·jvm·c++·学习
C++chaofan11 小时前
Redisson分布式限流
java·jvm·spring boot·redis·分布式·mvc·redisson
tanxiaomi1 天前
通过HTML演示JVM的垃圾回收-新生代与老年代
前端·jvm·html
_extraordinary_1 天前
Java JVM --- JVM内存区域划分,类加载,GC垃圾回收
java·开发语言·jvm
羚羊角uou1 天前
【Linux】多线程创建及封装
jvm
Nᴏsᴛᴀʟɢɪᴀ念1 天前
多线程奇幻漂流:从单核到多核质变(一)
java·开发语言·jvm·多线程