Java 虚拟机 | 垃圾回收机制 | 七日打卡

简介: Java 虚拟机 | 垃圾回收机制 | 七日打卡

目录

image.png


前置知识


这篇文章的内容会涉及以下前置 / 相关知识,贴心的我都帮你准备好了,请享用~


1. 垃圾回收概述


垃圾回收机制(Garbage Collection,GC) 是一种自动的内存管理机制,即:当内存中的对象不再需要时,就自动释放以让出存储空间。


垃圾回收机制是 Java 虚拟机的重要特性之一,同时也是面试重要考点之一。在实践中,由于 GC 会占用程序运行资源,欲进行更有深度的内存性能优化也需要对垃圾回收机制有一定理解。


在讨论垃圾回收机制的时候,需要讨论的以下三个问题,你可以带着这三个问题阅读后面的内容,思路会更清晰。


  • 回收的对象: 哪些对象 / 区域需要回收?
  • 回收的时机: 什么时候触发 GC?
  • 回收的过程: 如何回收?


1.1 GC 相关概念


这一节,我们先罗列一些 GC 相关知识中比较重要的概念:


概念 描述
collector 表示程序中负责垃圾回收的模块
mutator 表示程序中除了 collector 以外的模块
增量式回收(Incremental Collection) 每次 GC 只针对堆的一部分,而不是整个堆,大幅减少了停顿时间
分代回收(Generational GC) 增量式回收的实现方式之一,将堆分为新生代、老生代和永生代等部分
并行回收(Parallel Collection) collector 中有多个垃圾回收线程
并发回收(Concurrent Collection) 指垃圾回收工作的某个阶段,collector 线程和 mutator 可以同时执行。
这样避免了 collector 线程工作时需要暂停 mutator 线程(stop-the-world)


1.2 垃圾回收的优缺点


  • 优点: 不再需要为每个 new 操作编写对应的 delete / free 操作,程序不容易出现内存泄漏或内存溢出问题;
  • 风险: 垃圾回收处理程序本身也占用系统资源(CPU 资源 / 内存),增大程序暂停时间。


1.3 GC 算法性能指标


在介绍垃圾回收算法之前,我们先来定义评价垃圾回收方法的性能指标:


指标 定义 描述
吞吐量(throughput) 指单位时间内的处理能力 吞吐量=运行用户代码时间垃圾回收频率∗单次垃圾回收时间吞吐量 = \frac{运行用户代码时间} {垃圾回收频率 * 单次垃圾回收时间}=
最大暂停时间(pause time) 指因执行 GC 而暂停执行程序的最长时间 /
堆利用率(space overhead) 指有效使用的堆空间占整个堆的比例 影响因素:对象头大小 + 回收算法
访问局部性 指回收方法是否倾向于访问局部内存 访问局部内存更容易命中 CPU 缓存行


提示: 若不理解 “访问局部性” 的概念,可联想快速排序和堆排序的性能对比,前者的访问局部性更优。


2. 垃圾回收管理的区域(回收的对象)


根据《Java虚拟机规范》的规定,Java 虚拟机所管理的内存将会包括以下区域:


运行时数据区域 线程独占 描述
程序计数寄存器 私有 存储下一条字节码指令的内存地址
Java 虚拟机栈 私有 存储线程栈帧(Stack Frame )

栈帧包含:局部变量表、操作数栈、动态连接、返回地址等信息
本地方法栈 私有 存储本地方法栈帧
Java 堆 共享 大多数对象的存储区域
方法区 共享 存储类型信息、常量、类静态变量、即使编译器编译后的代码缓存等


并不是 Java 虚拟机管理的所有区域都需要垃圾回收,线程独占的区域会随着线程结束而销毁,不需要垃圾回收。因此垃圾回收机制需要管理的区域是:


  • 堆: 垃圾对象;
  • 方法区: 废弃的常量和不再使用的类型。


3. 如何判定垃圾对象?(回收的时机)


判断对象是否为垃圾对象的方法可以分为两种:引用计数 & 可达性分析。以判断方法为划分,后文所讲的垃圾回收算法也可以划分为 引用计数式 & 追踪式 两大类。


3.1 引用计数算法(Reference Counting)


3.1.1 判定方法

在分配对象时,会额外为对象分配一段空间,用于记录指向该对象的引用个数。如果有一个新的引用指向该对象,则计数器加 1;当一个引用不再指向该对象,则计数器减 1 。当计数器的值为 0 时,则该对象为垃圾对象。


3.1.2 优点

  • 1、及时性:当对象变成垃圾后,程序可以立刻感知,马上回收;而在可达性分析算法中,直到执行 GC 才能感知;
  • 2、最大暂停时间短:GC 可与应用交替运行。


3.1.3 缺点

  • 1、计数器值更新频繁:大多数情况下,对象的引用状态会频繁更新,更新计数器值的任务会变得繁重;
  • 2、堆利用率降低:计数器至少占用 32 位空间(取决于机器位数),导致堆的利用率降低;
  • 3、实现复杂;
  • 4、(致命缺陷)无法回收循环引用对象。


易错: 引用计数法是算法简单,实现较难。


3.2 可达性分析算法(Reachability Analysis)


3.2.1 判定方法


从 GC 根节点(GC Root)为起点,根据引用关系形成引用链。当一个对象存在到 GC Root 的引用链,则为存活对象,否则为垃圾对象。在 Java 中,GC Root 主要包括:


  • 1、Java 虚拟机栈中引用的对象(即栈帧中的本地变量表);
  • 2、本地方法栈中引用的对象;
  • 3、方法区中类静态变量引用的对象;
  • 4、方法区常量池中引用的对象;
  • 5、同步锁(synchronized 关键字)持有的对象;


3.2.2 优点


  • 1、可回收循环引用对象;
  • 2、实现简单。


3.2.3 缺点


  • 1、最大停顿时间长:在 GC 期间,整个应用停顿(stop-the-world,STW);
  • 2、回收不及时:直到执行 GC 才能感知垃圾对象;


3.3 小结


判定方法 优点 缺点
引用计数 1、及时性
2、最大暂停时间短
1、计数器值更新频繁
2、堆利用率降低
3、实现复杂
4、无法回收循环引用对象
可达性分析 1、可回收循环引用对象
2、实现简单
1、最大停顿时间长
2、回收不及时


由于引用计数式 GC 存在 「无法回收循环引用对象」 的致命缺陷,工业实现上还是追踪式 GC 占据了主流,后面我主要介绍的也是追踪式 GC。


4. 垃圾回收算法(回收的过程)


从原理上,垃圾回收算法可以分为以下四类基础算法,其它的垃圾回收算法其实是对基础算法的改进或组合。


时间 早期提出者 算法 类别
1960年 Lisp 之父 John McCarthy 标记 - 清理算法 追踪式
1960年 George E. Collins 引用计数算法 引用计数式
1969年 Fenichel 复制算法 追踪式
1974年 Edward Lueders 标记 - 整理算法 追踪式


在实践中,当代绝大多数垃圾收集器都采用了 “分代收集模型” ,该模型的经验前提是:


  • 1、绝大多数对象都是朝生夕死,无法熬过第一次垃圾回收;
  • 2、熬过了多次垃圾回收的对象,往往越难被回收。


在上述事实经验的基础上,虚拟机往往使用了 动静分离 的设计思想:将新对象和难以回收的老对象存储在不同的区域,新对象存放在新生代,难回收的对象存在老年代。并且针对不同区域的特性采用不同的垃圾回收算法。

image.png


—— 图片引用自网络


  • 1、新生代: 新生代中的对象存活率低,只要付出少量的复制成本就能完成回收过程,因此选用复制算法;
  • 2、老生代: 老生代中的对象存活率高,并且没有额外空间进行分配担保,因此选用 “标记 - 清理” 或 “标记 - 整理” 算法。


4.1 标记 - 清理算法(Mark-Sweep)


4.1.1 算法回收过程


标记 - 清理算法的回收过程主要分为两个阶段:

  • 标记(Mark)阶段: 遍历整个堆,标记出垃圾对象(也可以标记存活对象);
  • 清理(Sweep)阶段: 遍历整个堆,将垃圾对象分块链接空闲列表。


image.png

4.1.2 优点


实现简单;

4.1.3 缺点


  • 1、执行效率不稳定:Java 堆中对象越多,标记和清理的过程可能会越耗时;
  • 2、内存碎片化(fragmentation):回收过程会逐渐产生很多不连续的小内存,当小内存不足以分配对象内存时,又会触发一次垃圾回收动作(GC for Alloc)。


4.2 复制算法(Copying)


4.2.1 算法回收过程


复制算法的回收过程要点如下:


  • 1、将堆分为大小相同的两个空间:from 区和 to 区;
  • 2、对象的内存分配只使用 from 区,当 from 区占满时,将存活对象全部复制到 to 区;
  • 3、复制完成后互换 from 区和 to 区的指针。


image.png

—— 图片引用自 weread.qq.com/web/reader/… 邓凡平 著


4.2.2 优点

  • 1、快速分配对象:空闲分块是一个连续内存空间,不需要向标记-清理算法那样遍历空闲列表;
  • 2、避免内存碎片化:存活对象和新分配对象都被压缩到 tospace 的一端,避免出现很多不连续的小内存。


4.2.3 缺点

  • 1、堆利用率低:把堆做二等分只能利用其中的一半,堆利用率最高仅为 50 %。


4.2.4 改进

  • 1、将新生代分为:一块 Eden 区和两块 Survivor 区,对应的比例为 8:1:1;
  • 2、对象只在 Eden 区分配,当 Eden 区占满后,将 Eden 区和 from Survivor 区的存活对象全部赋值到 to Survivor 区;
  • 3、复制完成后互换 from Survivor 区和 to Survivor 区的指针。

改进后堆利用率提升到最高 90%。


4.3 标记 - 整理算法(Mark-Compact)


4.3.1 算法回收过程


标记 - 清除算法与标记 - 整理算法的本质差异在于是否移动对象。标记 - 整理算法的回收过程主要分为两个阶段:


  • 标记(Mark)阶段: 遍历整个堆,标记出垃圾对象(这个步骤与标记 - 清理算法相同);
  • 整理(Compact)阶段: 将所有存活对象移动(压缩)到堆的一端,然后直接清理掉边界以外的内存。


image.png

—— 图片引用自 weread.qq.com/web/reader/… 邓凡平 著

4.3.2 优点

  • 1、避免内存碎片化,堆利用率高,吞吐量更高;
  • 2、快速分配对象:空闲分块是一个连续内存空间,不需要向标记-清理算法那样遍历空闲列表;


4.3.3 缺点

  • 1、移动对象比清理对象更耗时,导致 GC 停顿时间(Stop-the-world)时间更长。


5. 并发回收


5.1 stop-the-world 现象


在标准的垃圾回收算法中,在垃圾回收线程(collector)进行标记 - 清理 / 整理 / 复制的过程中需要暂停所有的用户线程(mutator),这是为了保证能够彻底清理所有垃圾对象。


但是这种做法却会导致虚拟机的吞吐量降低(吞吐量=运行用户代码时间垃圾回收频率∗单次垃圾回收时间吞吐量 = \frac{运行用户代码时间} {垃圾回收频率 * 单次垃圾回收时间}=)。


5.2 CMS 垃圾收集器


在追求响应速度的系统上,希望垃圾收集器暂停时间尽可能小,为此发展出了允许回收线程与用户线程并发运行的垃圾收集器 —— CMS(Concurrent Mark Sweep,并发标记清除)。


CMS 垃圾收集器的主要工作过程分为 4 个步骤:


  • 1、初始标记(短暂 stop-the-world): 仅仅标记被 GC Root 直接引用的对象,由于 GC Root 相对较少,这个过程速度很块;
  • 2、并发标记(耗时): 继续遍历 GC Root 引用链上的对象,这个过程比较耗时,所以采用并发处理;
  • 3、重新标记(短暂 stop-the-world): 为了修正并发标记期间用户线程导致的引用关系变化,需要暂停用户线程重新标记;
  • 4、并发清除(耗时) 由于清除对象的过程比较耗时,所以采用并发处理。


image.png


—— 图片引用自网络

5.3 CMS 的优点


  • 1、缩短了系统 stop-the-world 时间,提高了吞吐量;


5.4 CMS 的缺点


  • 1、CPU 敏感: 采用了并发策略,系统整体上会占用更多 CPU 资源;
  • 2、浮动垃圾: 由于并发清理的过程中用户线程还在运行,CMS 无法回收这个阶段中用户线程产生的垃圾,这一部分垃圾称为 “浮动垃圾”。由于浮动垃圾的存在,垃圾收集器需要预留出一部分空间来允许浮动垃圾的产生,如果预留的空间还不足以存放浮动垃圾,就会出现 Concurrent Mode Failure,此时需要临时启动非并发清理方案来代替 CMS;
  • 3、内存碎片: 采用标记 - 清理算法,会产生内存碎片。


6. 总结


  • 1、垃圾回收算法的性能指标主要有:吞吐量、最大暂停时间、堆利用率、访问局部性。在理解垃圾回收机制的过程中,可以带着 “回收的对象” & “回收的时机” & “回收的过程” 三个问题来理解;
  • 2、垃圾回收机制管理的区域有堆和方法区;
  • 3、判断垃圾对象的算法分为引用计数算法和可达性分析算法,两者各有优缺点;
  • 4、垃圾回收算法可以分为四类基本算法:引用计数算法、标记-清理算法、标记-整理算法和复制算法。其它的垃圾回收算法都是对基础算法的改进或组合。比如主流的虚拟机垃圾回收算法采用分代回收模型:即在新生代选用复制算法(对象存活率低),而老生代选用 “标记 - 清理” 或 “标记 - 整理” 算法(对象存活率高,并且没有额外空间进行分配担保);
  • 5、在标准的垃圾回收算法中,垃圾回收过程会 stop-the-world。使用并发收集可以降低系统的暂停时间,提供吞吐量。
目录
打赏
0
0
0
0
45
分享
相关文章
JVM实战—4.JVM垃圾回收器的原理和调优
本文详细探讨了JVM垃圾回收机制,包括新生代ParNew和老年代CMS垃圾回收器的工作原理与优化方法。内容涵盖ParNew的多线程特性、默认线程数设置及适用场景,CMS的四个阶段(初始标记、并发标记、重新标记、并发清理)及其性能分析,以及如何通过合理分配内存区域、调整参数(如-XX:SurvivorRatio、-XX:MaxTenuringThreshold等)来优化垃圾回收。此外,还结合电商大促案例,分析了系统高峰期的内存使用模型,并总结了YGC和FGC的触发条件与优化策略。最后,针对常见问题进行了汇总解答,强调了基于系统运行模型进行JVM参数调优的重要性。
104 10
JVM实战—4.JVM垃圾回收器的原理和调优
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
JVM实战—3.JVM垃圾回收的算法和全流程
本文详细介绍了JVM内存管理与垃圾回收机制,涵盖以下内容:对象何时被垃圾回收、垃圾回收算法及其优劣、新生代和老年代的垃圾回收算法、Stop the World问题分析、核心流程梳理。
JVM实战—3.JVM垃圾回收的算法和全流程
|
4月前
|
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
109 0
JVM实战—5.G1垃圾回收器的原理和调优
本文详细解析了G1垃圾回收器的工作原理及其优化方法。首先介绍了G1通过将堆内存划分为多个Region实现分代回收,有效减少停顿时间,并可通过参数设置控制GC停顿时长。接着分析了G1相较于传统GC的优势,如停顿时间可控、大对象不进入老年代等。还探讨了如何合理设置G1参数以优化性能,包括调整新生代与老年代比例、控制GC频率及避免Full GC。最后结合实际案例说明了G1在大内存场景和对延迟敏感业务中的应用价值,同时解答了关于内存碎片、Region划分对性能影响等问题。
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
91 0
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
171 29
JVM简介—1.Java内存区域