垃圾回收机制GC
目录
1.GC是什么?
GC 是 Garbage Collection 的缩写,由虚拟机“自动化”完成垃圾回收工作。GC 是分代收集器,根据对象的存活周期的不同将内存划分为几块。 一般是把 Java 堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。
Java提供的垃圾回收功能可以自动监测对象是否超过了作用域,从而达到自动回收内存的目的,Java的垃圾回收器会自动进行管理,调用方法:System.gc()
或者Runtime.getRuntime().gc()
。
2. GC是干什么的?
JAVA创建一个类或者对象后,难免会存在以后不使用的情况,为了减少其继续再占用内存,必须建立一套清理垃圾的机制。
由于开发人员没有在代码中显式删除内存,所以垃圾收集器会去发现不需要(垃圾)的对象,然后删除它们,释放内存。在Java中99%的对象是临时对象。GC 垃圾回收主要是在堆里,主要是在 Eden
区、Old Generation
区。所以,需要先了解一下堆的结构。
堆的结构图如下:

在HotSpot虚拟机中,物理上将内存分为两个:新生代(young generation)和老年代(old generation)。
新生代(young generation)
-
包括两部分区域 —
Eden
区、Survivor
区。其中,Survivor
区又分成了两块。 -
新创建的对象都存放在这里,主要是在
Eden
区。每次垃圾收集时都发现有大批对象死去,只有少量存活。 -
当对象从这块内存区域消失时,触发了一次“轻GC”。
老年代(old generation):
- 大对象,就是需要大量连续内存空间的对象(比如:字符串、数组),会直接进入老年代。
- 对象存活率高、没有额外空间对它进行分配担保。
- 这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。
- 对象从老年代消失时,触发了一次“重GC”。
3. 什么时候需要监控GC
-
当需要排查各种内存溢出,内存泄露问题时。
-
当垃圾成为系统达到更高并发量的瓶颈时。
我们就需要对GC的自动回收实施必要的监控和调节。
4. 什么时候触发GC?
- 程序调用
System.gc()
或者Runtime.getRuntime().gc()
时可以触发。 - 系统自身来决定GC触发的时机。
-
当
Eden
区 满了后,会触发一次轻GC,在Eden
区中存活的对象会到Survivor
区中。 -
当新生代满了之后,会触发一次重GC:把新生代清一次,活下来的对象会到 老年代。
5. 如何判断对象是否存活?
两种方法:引用计数法 和 可达性分析算法。
5.1 引用计数法
1)引用计数法是什么?
给每个对象中添加一个引用计数器,记录有多少地方引用它。
2)算法过程是什么样子的?
给每个对象中添加一个引用计数器,每当有地方引用它时,计数器值就加一;当引用释放/失效时,计数器值就减一。当对象计数器为0时,就可以回收。
3)引用计数法的优缺点是什么?
优点:实现简单
缺点:无法解决互相引用的问题
5.2 可达性分析算法
1)什么是GC Root对象?
GC Root
对象:正在执行的Java程序可以访问的引用变量。
GC Root对象有哪些?
- 虚拟机栈 / 本地方法栈中引用的对象
- 已加载类的静态变量
- 已启动并且没有停止的线程
2)算法过程是什么样子的?
通过一系列称为“GC Root
”的对象作为起点,基于对象引用关系,开始向下搜索,所走过的路径称为引用链,当一个对象到GC Root
没有任何引用链相连,证明对象是不可用的。
3)不可达对象是“非死不可”的吗?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize
方法。当对象没有覆盖 finalize
方法,或 finalize
方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
4)可达性分析算法的优缺点?
优点:解决了循环引用
缺点:在多线程环境下,其他线程会更新已经访问过的对象的引用,从而造成误报或漏报
6. 如何回收垃圾?— GC算法
6.1 复制算法
1)复制算法是什么?
让 To Survivror
区一直保持为空,每次GC, 把From Survivror
区中存活的对象复制到To Survivror
,然后清空From Survivror
区。
2)算法过程是什么样子的?
在堆里,谁空谁是 To Survivror
- 把
Eden
区中的内容复制到To Survivor
区中 - 把
From Survivror
区中的内容复制到To Survivor
区中 - 互换
From Survivror
区 和To Survivor
区的名字,此时,空的那一块内存还是To Survivror
区。
如果一个对象经过15次(可以设置次数)GC 后没死,就会到老年代。
3)优缺点是什么?
优点:没有内存碎片
缺点:浪费内存空间(一半的空间是空的)
4)使用场景是什么?
复制算法适合使用在对象存活度较低的区域、新生代。
6.2 标记清除算法
1)标记清除算法是什么?
标记有reference
引用的对象,没有标记的就是回收对象,需要清除。
2)算法过程是什么样子的?
- 扫描。标记活着的对象。
- 清除。回收没有标记的对象。
3)优缺点是什么?
优点:不需要额外的空间
缺点:2次扫描,浪费时间,会产生内存碎片
6.3 标记清除压缩算法
1)标记清除压缩算法是什么?
堆是所有线程共享的一块内存区域,也是JVM所管理的内存中最大的一块。
2)算法过程是什么样子的?
- 扫描。标记活着的对象。
- 清除。回收没有标记的对象。
- 压缩内存空间。再扫描,移动对象。
6.4 比较三种算法
- 内存效率:复制算法(扫描1次) > 标记清除算法(扫描2次) > 标记清除压缩算法(扫描3次)
- 内存整齐度:复制算法 = 标记清除压缩算法 > 标记清除算法
- 内存利用率:标记清除压缩算法 = 标记清除算法 > 复制算法
6.5 堆中使用哪种算法
GC 是分代收集算法。也就是,根据各个年代的特点选择合适的垃圾收集算法。
新生代:使用 复制算法。
老生代:使用 标记清除算法 和 标记清除压缩算法 混合实现。
7. 常见的垃圾回收器
7.1 Serial 收集器
1)Serial 收集器是什么?
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
2)有什么优点呢?
与其他收集器的单线程相比,简单而高效(因为没有线程交互的开销)。
3)使用场景
运行在 Client 模式下的虚拟机。
7.2 ParNew 收集器
1)ParNew收集器是什么?
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
2)使用场景
许多运行在 Server 模式下的虚拟机的首要选择。
7.3 Parallel Scavenge 收集器
1)Parallel Scavenge 收集器是什么?
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。
但是,Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。
2)使用场景
如果对于收集器运作不太了解,手工优化存在困难的时候,可以使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成。
7.4 Serial Old 收集器
1)Serial Old 收集器是什么?
Serial 收集器的老年代版本,它同样是一个单线程收集器。
2)Serial Old 收集器有什么用?
- 在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用
- CMS 收集器的后备方案
7.5 Parallel Old 收集器
1)Parallel Old 收集器是什么?
Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。
2)使用场景
在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
7.6 CMS 收集器
1)CMS 收集器是什么?
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
2)运行过程是什么样子的?
CMS 收集器是一种 “标记-清除”算法实现的。运行过程如下:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
3)优缺点是什么?
优点:并发收集、低停顿
缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 会导致收集结束时会有大量空间碎片产生。
7.7 G1 收集器
1)G1 收集器是什么?
G1 (Garbage-First) 是一款面向服务器的垃圾收集器, 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
2)使用场景
主要针对配备多颗处理器及大容量内存的机器。
3)特点是什么?
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
4)运行过程是什么样子的?
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
7.7 ZGC 收集器
1)ZGC 收集器是什么?
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。在 ZGC 中出现 Stop The World 的情况会更少。
8. 出现OOM(OutOfMemoryError) ,怎么排除?
- 能看到代码是在第几行出错:内存快照分析工具(如MAT、Jprofiler)。可以分析Dump内存文件,快速定位内存泄漏。
- Debug,一行行分析代码。
参考链接:
https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6
https://www.bilibili.com/video/BV1iJ411d7jS?from=search&seid=12696469813953935602