美文网首页
「Java 路线」| 垃圾回收机制

「Java 路线」| 垃圾回收机制

作者: 彭旭锐 | 来源:发表于2020-12-22 23:35 被阅读0次

点赞关注,不再迷路,你的支持对我意义重大!

🔥 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) 指单位时间内的处理能力 吞吐量 = \frac{HEAP\_SIZE} {GC消耗时间}
最大暂停时间(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虚拟机


创作不易,你的「三连」是丑丑最大的动力,我们下次见!

相关文章

网友评论

      本文标题:「Java 路线」| 垃圾回收机制

      本文链接:https://www.haomeiwen.com/subject/cymqnktx.html