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天前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
4天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
19 1
|
1天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
9 0
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
72 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
2月前
|
存储 Java PHP
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
65 0
|
3月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制(GC)
本文将探讨Java的自动内存管理核心——垃圾回收机制。通过详细解析标记-清除算法、复制算法和标记-整理算法等常用垃圾回收算法,以及CMS、G1等常见垃圾回收器,帮助读者更好地理解Java应用的性能优化和内存管理。同时,探讨分代收集、分区收集等策略在实际项目中的应用。结语部分总结了垃圾回收机制在Java开发中的重要性,并展望了未来可能的发展。
67 0
|
4月前
|
缓存 监控 Java
"Java垃圾回收太耗时?阿里HBase GC优化秘籍大公开,让你的应用性能飙升90%!"
【8月更文挑战第17天】阿里巴巴在HBase实践中成功将Java垃圾回收(GC)时间降低90%。通过选用G1垃圾回收器、精细调整JVM参数(如设置堆大小、目标停顿时间等)、优化代码减少内存分配(如使用对象池和缓存),并利用监控工具分析GC行为,有效缓解了高并发大数据场景下的性能瓶颈,极大提升了系统运行效率。
87 4
|
4月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
|
5月前
|
监控 算法 Java
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
391 4