
点赞关注,不再迷路,你的支持对我意义重大!
🔥 Hi,我是丑丑。本文 「Java 路线」| 导读 —— 他山之石,可以攻玉 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)
前言
- 垃圾回收机制(Garbage Collection,GC) Java 虚拟机的重要特性之一,同时也是面试重要考点之一。在实践中,由于 GC 会占用程序运行资源,欲进行更有深度的内存性能优化也需要对垃圾回收机制有一定理解;
- 在这篇文章里,我将系统分析 Java 垃圾回收机制 & 主流的垃圾回收算法。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。
系列文章
目录

1. 概述
1.1 什么是垃圾回收?
垃圾回收(Garbage Collection,GC) 是一种自动的内存管理机制,即:当内存中的对象不再需要时,就自动释放以让出存储空间。
-
优势: 不再需要为每一个 new 操作编写对应的 delete / free 操作,不容易出现内存泄漏和内存溢出问题;
-
风险: 垃圾回收程序本身也占用资源(CPU 资源 / 额外内存),有时还会阻塞 Java 程序的执行。
在讨论垃圾回收机制的时候,需要讨论的以下三个问题,你可以带着这三个问题阅读后面的内容,思路会更清晰。
- 触发时机: 什么时候触发 GC?
- 收集: 哪些对象 / 区域需要回收?
- 回收: 如何回收?
1.2 GC 相关概念
这一节,我们先罗列一些 GC 相关知识中比较重要的概念:
概念 | 描述 |
---|---|
collector | 表示程序中负责垃圾回收的模块 |
mutator | 表示程序中除了 collector 以外的模块 |
增量式回收 (Incremental Collection) |
每次 GC 只针对堆的一部分,而不是整个堆,大幅减少了停顿时间 |
分代回收 (Generational GC) |
增量式回收的实现方式之一,将堆分为新生代、老生代和永生代等部分,每次 GC 往往只针对其中一代 |
并行回收 (Parallel Collection) |
collector 中有多个垃圾回收线程 |
并发回收 (Concurrent Collection) |
指垃圾回收工作的某个阶段,collector 线程和 mutator 可以同时执行。这样避免了 collector 线程工作时需要暂停 mutator 线程(stop-the-world) |
1.3 JVM 运行时数据区域简介
根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下区域:
区域 | 线程独占 | 描述 |
---|---|---|
程序计数寄存器 (Program Counter Register) |
独占 | 存储下一条字节码指令的内存地址 |
Java 虚拟机栈 (Java Virtual MachineStack) |
独占 | 存储线程栈帧(Stack Frame ) 栈帧包含:局部变量表、操作数栈、动态连接、方法出口等信息 |
本地方法栈(Native Method Stacks) | 独占 | 存储本地方法栈帧 |
Java 堆(Java Heap) | 共享 | 大多数对象的存储区域 |
方法区(Method Area) | 共享 | 存储类型信息、常量、类静态变量、即使编译器编译后的代码缓存等 |
1.4 垃圾回收管理的区域
并不是 Java 虚拟机管理的所有区域都需要垃圾回收,线程独占的区域会随着线程结束而销毁,不需要垃圾回收。因此垃圾回收机制需要管理的区域是:
-
堆: 垃圾对象;
-
方法区: 废弃的常量和不再使用的类型。
1.5 GC 算法性能指标
在介绍垃圾回收算法之前,我们先来定义评价垃圾回收方法的性能指标:
指标 | 定义 | 描述 |
---|---|---|
吞吐量(throughput) | 指单位时间内的处理能力 | |
最大暂停时间(pause time) | 指因执行 GC 而暂停执行程序的最长时间 | / |
堆利用率(space overhead) | 指有效使用的堆空间占整个堆的比例 | 影响因素:对象头大小 + 回收算法 |
访问局部性 | 指回收方法是否倾向于访问局部内存 | 访问局部内存更容易命中 CPU 缓存行 |
提示: 若不理解 “访问局部性” 的概念,可联想快速排序和堆排序的性能对比,前者的访问局部性更优。
2. 如何判定垃圾对象?
判断对象是否为垃圾对象的方法可以分为两种:引用计数 & 可达性分析。以判断方法为划分,后文所讲的垃圾回收算法也可以划分为 引用计数式 & 追踪式 两大类。
2.1 引用计数算法(Reference Counting)
在分配对象时,会额外为对象分配一段空间,用于记录指向该对象的引用个数。如果有一个新的引用指向该对象,则计数器加 1;当一个引用不再指向该对象,则计数器减 1 。当计数器的值为 0 时,则该对象为垃圾对象。
- 优点:
1、及时性:当对象变成垃圾后,程序可以立刻感知,马上回收;而在可达性分析算法中,直到执行 GC 才能感知;
2、增量回收:GC 可与应用交替运行,最大暂停时间短。
- 缺点:
1、计数器值更新频繁:大多数情况下,对象的引用状态会频繁更新,更新计数器值的任务会变得繁重;
2、堆利用率降低:计数器至少占用 32 位空间(取决于机器位数),导致堆的利用率降低;
3、实现复杂;
4、(致命缺陷)无法回收循环引用对象。
易错: 引用计数法是算法简单,实现较难。
2.2 可达性分析算法(Reachability Analysis)
从 GC 根节点(GC Root)为起点,根据引用关系形成引用链。当一个对象存在到 GC Root 的引用链,则为存活对象,否则为垃圾对象。
- GC Root:
在 Java 中,GC Root 包括以下几种:
1、Java 虚拟机栈中引用的对象(即栈帧中的本地变量表);
2、本地方法栈中引用的对象;
3、方法区中类静态变量引用的对象;
4、方法区常量池中引用的对象。
- 优点:
1、可回收循环引用对象;
2、实现简单。
- 缺点:
1、最大停顿时间长:在 GC 期间,整个应用停顿(stop-the-world,STW);
2、回收不及时:直到执行 GC 才能感知垃圾对象;
2.3 小结
判定方法 | 优点 | 缺点 |
---|---|---|
引用计数 | 1、及时性 2、增量回收 |
1、计数器值更新频繁 2、堆利用率降低 3、实现复杂 4、无法回收循环引用对象 |
可达性分析 | 1、可回收循环引用对象 2、实现简单 |
1、最大停顿时间长 2、回收不及时 |
由于引用计数式 GC 存在「无法回收循环引用对象」 的致命缺陷,工业实现上还是追踪式 GC 占据了主流,后面我主要介绍的也是追踪式 GC。
3. 垃圾回收算法
从原理上,垃圾回收算法可以分为以下四类基础算法,其它的垃圾回收算法其实是对基础算法的改进或组合。
时间 | 早期提出者 | 算法 | 类别 |
---|---|---|---|
1960年 | Lisp 之父 John McCarthy | 标记 - 清理算法 | 追踪式 |
1960年 | George E. Collins | 引用计数算法 | 引用计数式 |
1969年 | Fenichel | 复制算法 | 追踪式 |
1974年 | Edward Lueders | 标记 - 整理算法 | 追踪式 |
3.1 标记 - 清理算法(Mark-Sweep Collection)
https://mp.weixin.qq.com/s/iklfWLmSD4XMAKmFcffp9g
标记 - 清理算法的回收过程主要分为两个阶段:
-
标记(Mark)阶段:遍历整个堆,标记出垃圾对象(也可以标记存活对象);
-
清理(Sweep)阶段: 遍历整个堆,将垃圾对象分块链接空闲列表。

-
优点: 实现简单;
-
缺点:
1、执行效率不稳定:Java 堆中对象越多,标记和清理的过程可能会越耗时;2、内存碎片化(fragmentation):回收过程会逐渐产生很多不连续的小内存,当小内存不足以分配对象内存时,又会触发一次垃圾回收动作(GC for Alloc)。
4. 引用计数算法(Reference Counting)
5. 复制算法(Copying Collection)
复制算法的回收过程要点如下:
1、将堆分为大小相同的两个空间:fromspace & tospace,对象的内存分配只使用 fromspace;
2、当 fromspace 占满时,将存活对象全部复制到 tospace。复制完成后互换 fromspace 和 tospace 的指针;
3、注意,这个过程不需要标记。

- 优点:
1、快速分配对象:空闲分块是一个连续内存空间,不需要向标记-清理算法那样遍历空闲列表;
2、避免内存碎片化:存活对象和新分配对象都被压缩到 tospace 的一端,避免出现很多不连续的小内存。
- 缺点:
1、堆利用率低:把堆做二等分只能利用其中的一半,堆利用率最高仅为 50 %(第 节 改进)。
6. 标记 - 整理算法(Mark-Compact Collection)
标记 - 整理算法的回收过程主要分为两个阶段:
-
标记(Mark)阶段: 遍历整个堆,标记出垃圾对象(这个步骤与标记 - 清理算法相同);
-
整理(Compact)阶段: 将所有存活对象移动(压缩)到堆的一端,然后直接清理掉边界以外的内存。

标记-清除算法与标记-整理算法的本质差异在于是否移动对象,前者是非移动式的,后者是移动式的。
- 优点:
1、避免内存碎片化,堆利用率高,吞吐量更高;
2、快速分配对象:空闲分块是一个连续内存空间,不需要向标记-清理算法那样遍历空闲列表;
- 缺点:
1、移动对象比清理对象更耗时,导致 GC 停顿时间(Stop-the-world)时间更长。
7.
8. 总结
回收算法 | 优点 | 缺点 |
---|
参考资料 | 作者 |
---|---|
《调试 ART 垃圾回收》 | Android Developers |
《深入理解Android:Java虚拟机 ART》(第 14 章) | 邓凡平 |
《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》(第 2、3 章) | 周志明 |
《垃圾回收的算法与实现》 | [日] 中村成洋,[日] 相川光 |
《Android 移动性能实战》(第 2 章) | 腾讯 SNG 专项测试团队 |
《Dalvik 与 ART 虚拟机的GC调试日志》 | Gityuan(字节跳动) |
《咱们从头到尾说一次 Java 垃圾回收》 | 聂晓龙(阿里巴巴) |
《垃圾回收算法是如何设计的?》 | 齐光(阿里巴巴) |
《垃圾回收器是如何演进的?》 | 齐光(阿里巴巴) |
《支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」》 | 入弦(阿里巴巴) |
《美团对 Java 新一代垃圾回收器 ZGC 的探索与实践》 | 王东、王伟(美团) |
《Android上的 ART 虚拟机》 | 强波(华为) |
《Android上的 Dalvik 虚拟机》 | 强波(华为) |
https://paul.pub/android-java-vm/
应用与系统稳定性第六篇---JVM垃圾回收之finalize执行时引起timed out 闪退分析
Android ART 并行拷贝垃圾回收
Android上的Dalvik虚拟机
Android上的ART虚拟机
创作不易,你的「三连」是丑丑最大的动力,我们下次见!

网友评论