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

相关文章
|
4天前
|
存储 算法 Java
先有JVM还是先有垃圾回收器?
是先有垃圾回收器再有JVM呢,还是先有JVM再有垃圾回收器呢?或者是先有垃圾回收再有JVM呢?历史上还真是垃圾回收更早面世,先有垃圾回收再有JVM。下面我们就来刨析刨析JVM的垃圾回收~
13 0
先有JVM还是先有垃圾回收器?
|
4天前
|
安全 算法 Java
深入浅出JVM(十三)之垃圾回收算法细节
深入浅出JVM(十三)之垃圾回收算法细节
|
4天前
|
存储 算法 Java
深入浅出JVM(十二)之垃圾回收算法
深入浅出JVM(十二)之垃圾回收算法
|
4天前
|
算法 Java PHP
JVM 的垃圾回收机制以及垃圾回收算法的详解
JVM 的垃圾回收机制以及垃圾回收算法的详解
10 0
|
4天前
|
算法 数据安全/隐私保护 计算机视觉
基于二维CS-SCHT变换和LABS方法的水印嵌入和提取算法matlab仿真
该内容包括一个算法的运行展示和详细步骤,使用了MATLAB2022a。算法涉及水印嵌入和提取,利用LAB色彩空间可能用于隐藏水印。水印通过二维CS-SCHT变换、低频系数处理和特定解码策略来提取。代码段展示了水印置乱、图像处理(如噪声、旋转、剪切等攻击)以及水印的逆置乱和提取过程。最后,计算并保存了比特率,用于评估水印的稳健性。
|
1天前
|
算法
m基于BP译码算法的LDPC编译码matlab误码率仿真,对比不同的码长
MATLAB 2022a仿真实现了LDPC码的性能分析,展示了不同码长对纠错能力的影响。短码长LDPC码收敛快但纠错能力有限,长码长则提供更强纠错能力但易陷入局部最优。核心代码通过循环进行误码率仿真,根据EsN0计算误比特率,并保存不同码长(12-768)的结果数据。
19 9
m基于BP译码算法的LDPC编译码matlab误码率仿真,对比不同的码长
|
2天前
|
算法
MATLAB|【免费】融合正余弦和柯西变异的麻雀优化算法SCSSA-CNN-BiLSTM双向长短期记忆网络预测模型
这段内容介绍了一个使用改进的麻雀搜索算法优化CNN-BiLSTM模型进行多输入单输出预测的程序。程序通过融合正余弦和柯西变异提升算法性能,主要优化学习率、正则化参数及BiLSTM的隐层神经元数量。它利用一段简单的风速数据进行演示,对比了改进算法与粒子群、灰狼算法的优化效果。代码包括数据导入、预处理和模型构建部分,并展示了优化前后的效果。建议使用高版本MATLAB运行。
|
4天前
|
算法 计算机视觉
基于高斯混合模型的视频背景提取和人员跟踪算法matlab仿真
该内容是关于使用MATLAB2013B实现基于高斯混合模型(GMM)的视频背景提取和人员跟踪算法。算法通过GMM建立背景模型,新帧与模型比较,提取前景并进行人员跟踪。文章附有程序代码示例,展示从读取视频到结果显示的流程。最后,结果保存在Result.mat文件中。
|
4天前
|
资源调度 算法 块存储
m基于遗传优化的LDPC码OMS译码算法最优偏移参数计算和误码率matlab仿真
MATLAB2022a仿真实现了遗传优化的LDPC码OSD译码算法,通过自动搜索最佳偏移参数ΔΔ以提升纠错性能。该算法结合了低密度奇偶校验码和有序统计译码理论,利用遗传算法进行全局优化,避免手动调整,提高译码效率。核心程序包括编码、调制、AWGN信道模拟及软输入软输出译码等步骤,通过仿真曲线展示了不同SNR下的误码率性能。
9 1
|
4天前
|
存储 算法 数据可视化
基于harris角点和RANSAC算法的图像拼接matlab仿真
本文介绍了使用MATLAB2022a进行图像拼接的流程,涉及Harris角点检测和RANSAC算法。Harris角点检测寻找图像中局部曲率变化显著的点,RANSAC则用于排除噪声和异常点,找到最佳匹配。核心程序包括自定义的Harris角点计算函数,RANSAC参数设置,以及匹配点的可视化和仿射变换矩阵计算,最终生成全景图像。
http://www.vxiaotou.com