JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法

简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容。

一、垃圾回收算法介绍

垃圾回收算法在Java中起到了至关重要的作用,它的主要任务是自动管理内存,避免内存泄漏和垃圾堆积。那么,Java是如何实现垃圾回收的呢?简单来说,垃圾回收要做的有两件事:

  • 找到内存中存活的对象:这部分的任务主要是通过“标记”过程来完成。在Java中,所有的对象都由引用进行连接,如果一个对象没有任何引用指向它,那么它就被认为是不可达的(也就是死亡对象)。标记阶段就是通过遍历所有的对象,找出并标记那些被引用的对象,从而确定哪些对象仍然存活。
  • 释放不再存活对象的内存:这部分的任务主要是通过“清除”过程来完成。一旦标记阶段确定了哪些对象是存活,那么所有未被标记的对象就被认为是不可达的,它们的内存就可以被安全地释放,以便程序能再次利用这部分空间。

1.垃圾回收算法的历史和分类

自1960年起,John McCarthy首次提出了标记-清除算法(Mark Sweep GC),标志着垃圾回收算法的诞生。随后,1963年Marvin L. Minsky引入了复制算法(Copying GC),这两种算法成为后续垃圾回收算法的基础。在此基础上,垃圾回收算法不断发展,出现了标记-整理算法(Mark Compact GC)、分代GC(Generational GC)等优化版本。这些算法在实现垃圾回收的过程中,均致力于提高内存利用率,降低停顿时间,以满足不同应用场景的需求。

2.垃圾回收算法的评价标准

在Java中,垃圾回收通过独立的GC线程完成,但无论采用何种GC算法,都会存在需要暂停所有用户线程的阶段。这一过程被称为“Stop-The-World”(STW)。如果该过程过长,将对用户体验产生负面影响。因此,评价垃圾回收算法的优劣需基于以下三个关键标准:

  • 吞吐量:吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值,即吞吐量 = 执行用户代码时间 / (执行用户代码时间 + GC时间)。吞吐量数值越高,垃圾回收的效率就越高。比如:虚拟机总共运行了 100 分钟,其中GC花掉 1 分钟,那么吞吐量就是 99%。
  • 最大暂停时间:最大暂停时间指的是所有在垃圾回收过程中的STW(Stop The World)时间最大值。STW是垃圾回收过程中需要停止所有的用户线程的时间。最大暂停时间越短,用户使用系统时受到的影响就越短。
  • 堆使用效率:不同垃圾回收算法,对堆内存的使用方式是不同的。比如标记清除算法,可以使用完整的堆内存。而复制算法会将堆内存一分为二,每次只能使用一半内存。从堆使用效率上来说,标记清除算法要优于复制算法。

上述三种评价标准,堆使用效率、吞吐量,以及最大暂停时间不可兼得。一般来说,堆内存越大,最大暂停时间就越长。想要减少最大暂停时间,就会降低吞吐量。不同的垃圾回收算法,适用于不同的场景。

二、垃圾回收算法详解

1.标记清除算法

标记清除算法是垃圾回收中的一种基础算法,其核心思想分为两个阶段:标记阶段和清除阶段。

  • 标记阶段:在标记阶段,算法通过可达性分析,从GC Root(垃圾回收起始点)开始,通过引用链遍历出所有存活的对象。
  • 清除阶段:在清除阶段,算法从内存中删除未被标记的对象,即非存活对象。

标记清除算法的优点

  • 只需要在标记阶段给每个对象维护一个标记位,然后在清除阶段删除未被标记的对象即可。

标记清除算法的缺点

  • 内存碎片化问题:由于内存是连续的,当对象被删除后,内存中会出现许多细小的可用内存单元。如果需要分配一个较大的空间,这些小内存单元可能无法满足需求,导致内存浪费和碎片化。
  • 分配速度慢:由于内存碎片的存在,需要维护一个空闲链表。在分配内存时,可能需要遍历整个链表才能找到合适的空闲内存空间,导致分配速度变慢。

尽管标记清除算法存在一些缺陷,但它仍广泛应用于垃圾回收的实现中。在许多情况下,通过与其他算法结合使用,可以克服其缺点并提高垃圾回收的效率和性能。

2.复制算法

复制算法是一种垃圾回收算法,其核心思想是将堆内存分为两个相同的空间,即From空间和To空间。在对象分配阶段,只能使用其中一个空间(通常是From空间)。在垃圾回收阶段,存活的对象被复制到另一个未使用的空间(To空间)。完成复制后,两个空间的角色互换,原先的From空间变成To空间,而原先的To空间变成新的From空间。

完整的复制算法执行过程如下:

  1. 将整个堆内存分割成两个等大的空间,即From空间和To空间。
  2. 在对象分配阶段,新创建的对象只能在From空间中分配。
  3. 当垃圾回收阶段开始时,所有从GC Root开始的存活对象将被复制到To空间中。
  4. 接着,将GC Root及其关联的对象也复制到To空间中。
  5. 最后,清理From空间中的所有对象,并将两个空间的名称互换。

案例:

将整个堆内存分割成两个等大的空间,即From空间和To空间。在对象分配阶段,新创建的对象只能在From空间中分配。

image.gif

当垃圾回收阶段开始时,所有从GC Root开始的存活对象将被复制到To空间中,接着,将GC Root及其关联的对象也复制到To空间中。

image.gif

清理From空间中的所有对象,并将两个空间的名称互换。

image.gif

复制算法的优点

  • 吞吐量高:由于只需要遍历一次存活对象并将其复制到To空间,因此性能较好。相比之下,标记-整理算法需要两次遍历过程,标记-清除算法不需要移动对象。
  • 不会发生内存碎片化:由于在复制后对象按顺序放置在To空间中,因此对象以外的区域都是可用空间,不存在内存碎片化问题。

复制算法的缺点

  • 内存使用效率低:由于每次只能使用一半的内存空间来创建对象,因此内存利用率相对较低。这意味着在其他算法可以处理更多对象时,复制算法可能会浪费一半的内存空间。

3.标记整理算法

标记整理算法也被称为标记压缩算法,旨在解决标记清除算法中容易出现的内存碎片化问题。其核心思想分为两个阶段:

  • 标记阶段:这个阶段与标记清除算法中的标记阶段类似。通过可达性分析,从GC Root开始,遍历引用链以标记所有存活的对象。
  • 整理阶段:在这一阶段,所有存活的对象被移动到堆内存的一端。这有助于消除内存碎片,并使内存空间连续可用。

案例:

将所有存活的对象移动到堆内存的一端。

image.gif

标记整理算法的优点

  • 内存使用效率高:整个堆内存都可以被充分利用,不会像复制算法那样只能使用半个堆内存。
  • 防止内存碎片化:由于在整理阶段将存活对象移动到堆的一侧,剩余的空间可以连续地用于分配新对象,从而避免了内存碎片化问题。

标记整理算法的缺点

  • 整理阶段的效率问题:虽然存在一些高效的整理算法(如Two-Finger、表格算法、ImmixGC等),但标记整理算法的整体性能可能不如复制算法或标记清除算法。例如,某些实现可能需要遍历整个堆中的对象多次,这可能导致性能瓶颈。

为了提高标记整理算法的性能,可以采用一些优化策略。例如,使用更高效的标记和整理算法,或者结合其他垃圾回收算法(如复制算法或分代收集算法)来提高整体效率。通过合理的算法选择和优化,可以更好地平衡垃圾回收的效率和内存使用效率。

4.分代垃圾回收算法

分代垃圾回收算法是一种优秀的垃圾回收算法,它将整个内存区域划分为年轻代和老年代,以更高效地管理内存中的对象。这种算法通过将不同生命周期的对象划分到不同的区域,来优化垃圾回收的效率和性能。

image.gif

分代回收时,新创建的对象首先会被放入Eden伊甸园区。随着在Eden区中对象数量的增加,如果Eden区已满,新创建的对象将无法放入,此时会触发年轻代的GCMinor GCYoung GC)。Minor GC会回收Eden区From区需要回收的对象,并将未被回收的对象放入To区

随后,S0会变成To区,S1变成From区。当Eden区再次满时,继续往里放入对象,会再次触发Minor GC。这次会回收Eden区和S1(From)中的对象,并将Eden和From区中剩余的对象放入S0。

在每次Minor GC中,都会为对象记录年龄,初始值为0,每次GC后加1。如果Minor GC后对象的年龄达到阈值(最大15,默认值与垃圾回收器有关),该对象将被晋升至老年代

当老年代中空间不足,无法放入新的对象时,会先尝试Minor GC。如果仍然无法满足空间需求,就会触发Full GC。Full GC会对整个堆进行垃圾回收。如果Full GC仍然无法回收老年代中的对象,当继续尝试放入对象时,就会抛出Out Of Memory异常

案例:

分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。

image.gif

当Eden区满时,Minor GC或Young GC会被触发。Minor GC会回收Eden区和From区中的对象,并将未被回收的对象放入To区。

image.gif

S0会变成To区,S1变成From区(未被回收的对象放在From区)。当Eden区再次满时,Minor GC会被触发,回收Eden区和S1(from)中的对象,并将剩余对象放入S0区。每次Minor GC会记录对象的年龄。

image.gif

Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。

image.gif

当老年代空间不足,Minor GC后仍然无法放入新对象时,会触发Full GC,对整个堆进行垃圾回收。如果Full GC无法回收老年代的对象,当继续放入新对象时,会抛出Out Of Memory异常。

image.gif

在JDK 8中,可以使用-XX:+UseSerialGC参数来启用分代回收的垃圾回收器,并在运行程序时使用Arthas工具来查看分代之后的内存情况。通过Arthas的memory命令,可以显示出三个区域的内存情况,包括年轻代、老年代和元空间。

调整内存区域的大小:

参数名 参数含义
-Xms 设置堆的最小和初始大小,必须是1024倍数且大于1MB
-Xmx 设置最大堆的大小,必须是1024倍数且大于2MB
-Xmn 新生代的大小
-XX:SurvivorRatio=

伊甸园区和幸存区的比例,默认为8;

案例:新生代1g内存,伊甸园区800MB,S0和 S1各100MB

-XX:+PrintGCDetails verbose:gc 打印GC日志

案例(JDK 8中):

public static void main(String[] args) throws IOException {
        List<Object> list = new ArrayList<>();
        int count = 0;
        while (true){
            System.in.read();
            System.out.println(++count);
            list.add(new byte[1024 * 1024 * 1]);
        }
    }
image.gif

调整内存区域的大小:

-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails
image.gif

image.gif

通过Arthas的memory命令,查看三个区域的内存情况:

image.gif
eden_space 伊甸园区
survivor_space 幸存区
tenured_gen 老年代

通过合理地调整内存区域的大小和配置参数,可以更好地平衡垃圾回收的效率和内存使用效率。分代垃圾回收算法的应用广泛,是一种有效的垃圾回收策略,适用于各种应用场景。


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容,希望对大家有所帮助。

相关文章
|
9月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
845 55
|
4月前
|
算法 数据可视化 测试技术
HNSW算法实战:用分层图索引替换k-NN暴力搜索
HNSW是一种高效向量检索算法,通过分层图结构实现近似最近邻的对数时间搜索,显著降低查询延迟。相比暴力搜索,它在保持高召回率的同时,将性能提升数十倍,广泛应用于大规模RAG系统。
416 10
HNSW算法实战:用分层图索引替换k-NN暴力搜索
|
4月前
|
机器学习/深度学习 缓存 算法
微店关键词搜索接口核心突破:动态权重算法与语义引擎的实战落地
本文详解微店搜索接口从基础匹配到智能推荐的技术进阶路径,涵盖动态权重、语义理解与行为闭环三大创新,助力商家提升搜索转化率、商品曝光与用户留存,实现技术驱动的业绩增长。
|
5月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率
|
4月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
5月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
9月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
236 0
|
11月前
|
存储 算法 Java
G1原理—5.G1垃圾回收过程之Mixed GC
本文介绍了G1的Mixed GC垃圾回收过程,包括并发标记算法详解、三色标记法如何解决错标漏标问题、SATB如何解决错标漏标问题、Mixed GC的过程、选择CollectSet的算法
G1原理—5.G1垃圾回收过程之Mixed GC
|
11月前
|
存储 算法 Java
G1原理—6.G1垃圾回收过程之Full GC
本文详细探讨了G1垃圾回收器对Full GC(FGC)的优化处理,涵盖FGC的前置处理、整体流程及并行化改进。重点分析了传统FGC串行化的局限性以及G1通过Region分区和RSet机制实现并行标记的优势,包括任务窃取提升效率、跨分区压缩以生成空闲Region等技术细节。此外,文章还介绍了G1的新特性——字符串去重优化,通过判断char数组一致性减少重复字符串占用内存,从而提升内存使用效率。总结部分全面回顾了G1在FGC中的各项优化措施及其带来的性能改善。
G1原理—6.G1垃圾回收过程之Full GC