JVM垃圾回收算法

垃圾回收

垃圾回收(Garbage Collection,GC)是内存管理的一个重要组成部分,它的主要目标是自动识别并回收那些不再被程序使用的对象所占用的内存资源。有效的垃圾回收可以防止内存泄漏,确保程序稳定运行,并优化内存的使用效率。

在JVM中,确定对象是否可以被回收,通常采用以下两种主要的算法:

  1. 引用计数算法(Reference Counting): 引用计数算法为每个对象维护一个引用计数器。每当有一个新的引用指向该对象时,计数器加一;当一个引用失效时,计数器减一。当对象的引用计数器为零时,意味着没有任何引用指向该对象,它就可以被垃圾回收器回收。

  2. 可达性分析算法(Reachability Analysis): 可达性分析算法从一组称为GC Roots的对象开始,这些对象通常是虚拟机栈中的元素、方法区的静态变量等。垃圾回收器会从这些GC Roots开始向下搜索,通过引用链遍历所有可达的对象。如果某个对象在这个过程结束后仍未被访问到,即从GC Roots到该对象没有引用链相连,那么这个对象就被认为是不可达的,也就是垃圾,可以被回收。

(图源网络) 

在实际的JVM实现中,由于引用计数算法无法处理循环引用的情况,所以JVM通常采用的是可达性分析算法。这种算法能够有效地处理循环引用,并且能够准确地识别出所有的垃圾对象。 

在JVM的垃圾回收过程中,还会涉及到垃圾回收器(Garbage Collector)的选择和配置,以及不同的垃圾回收策略,这些都是为了确保垃圾回收的效率和应用程序的性能。

JVM 的垃圾回收算法

标记-清除算法(Mark and Sweep)是一种经典的垃圾回收算法,它在JVM中用于自动管理内存。这个算法的核心思想是识别出不再被程序使用的对象,并进行回收。具体来说,标记-清除算法包括两个主要步骤:

  1. 标记阶段(Mark): 在这个阶段,垃圾回收器会从一组被称为GC Roots的对象开始,这通常包括虚拟机栈中的局部变量、方法区的静态变量等。垃圾回收器将这些根对象视为起点,沿着引用链向下搜索,将所有可以从GC Roots直接或间接访问到的对象标记为“活跃的”。这意味着这些对象仍在使用中,不是垃圾。

  2. 清除阶段(Sweep): 一旦标记阶段完成,垃圾回收器将遍历整个堆空间,查找并回收所有未被标记为“活跃的”对象。这些对象被认为是垃圾,它们占用的空间将被释放,以便重新分配给新的对象。

尽管标记-清除算法简单且易于实现,但它存在一个显著的缺点:它会产生内存碎片。由于对象可以在堆中的任何位置被分配,当一个对象被回收后,它留下的空间可能会导致堆中出现不连续的空闲区域。随着时间的推移,这些小的空闲区域可能不足以容纳新的对象,导致内存利用率下降,甚至可能出现虽然堆中有足够的总空间,但无法分配一个新对象的情况。这种现象称为内存碎片化。

(图源网络)

复制算法(Copying)通过将内存分为两个相等的部分来管理对象的生存周期。这种算法的核心思想是利用了大部分对象的生命周期都很短的特性,即“分代假设”。以下是复制算法的基本工作流程:

  1. 分配: 当需要为新对象分配内存时,垃圾回收器首先在两个内存区域中选择一个作为当前使用的区(通常称为“From”区)。新对象会在这个区域内分配空间。

  2. 使用: 随着时间的推移,不断有新的对象被创建并分配到当前使用的区,同时也有对象因为不再被引用而成为垃圾。

  3. 复制: 当当前使用的区填满时,垃圾回收器会执行复制操作。它会遍历这个区域,将所有仍然活跃的对象复制到另一个区(通常称为“To”区)。这个过程也相当于进行了一次轻微的内存整理,因为复制过去的对象会在新的区域中紧凑地排列。

  4. 清理: 复制完成后,原来使用的区现在包含了很多已经不再使用的对象。垃圾回收器会清理这个区域,释放所有空间,为下一轮垃圾回收准备。

  5. 交换: 为了下一次垃圾回收,垃圾回收器会交换两个区的角色,即将原来的“To”区变为新的“From”区,原来的“From”区变为新的“To”区。

复制算法的优点是可以简化内存分配,因为新对象总是分配在连续的内存区域中,而且可以避免内存碎片问题。然而,它的主要缺点是内存使用效率较低,因为任何时候都有一个区是空闲的,这意味着理论上只有一半的内存空间被使用。这种算法通常适用于年轻代的垃圾回收,因为年轻代中对象的生命周期较短,频繁地进行复制和清理是可行的。对于老年代中生命周期较长的对象,则可能需要采用不同的垃圾回收策略。

(图源网络)

标记-整理算法(Mark and Compact)是JVM中用于垃圾回收的另一种算法,它旨在解决标记-清除算法中产生的内存碎片问题。这种算法不仅会标记不再使用的对象,还会在清理阶段将这些对象移动到内存的一端,从而优化内存的使用效率。以下是标记-整理算法的主要步骤:

  1. 标记阶段(Mark): 垃圾回收器从一组称为GC Roots的起点开始遍历,这通常包括虚拟机栈中的局部变量、方法区的静态变量等。它会沿着引用链向下搜索,将所有可以从GC Roots直接或间接访问到的对象标记为“活跃的”。这意味着这些对象仍在使用中,不是垃圾。

  2. 整理阶段(Compact): 在标记阶段完成后,垃圾回收器会遍历堆空间,将所有被标记为“活跃的”对象向堆的一端移动,这样可以确保所有的活跃对象都在内存的连续区域中。这个过程中,垃圾对象不会被立即清理,而是留在原位置。

  3. 清除阶段(Sweep): 一旦所有的活跃对象都被移动到了堆的一端,垃圾回收器会清除掉边界以外的所有未被移动的对象,即那些没有被标记为“活跃的”对象。这些对象被认为是垃圾,它们占用的空间将被释放,以便重新分配给新的对象。

标记-整理算法的优点是可以有效地减少内存碎片,因为它确保了所有活跃对象都位于内存的连续区域中。这有助于提高内存分配的效率,因为新对象的分配不需要在碎片化的内存中进行搜索。然而,这种算法可能会比标记-清除算法花费更多的时间,因为它需要在整理阶段移动对象。此外,由于对象的位置发生了变化,这可能会导致引用更新,进而可能影响程序的性能。

总的来说,标记-整理算法是一种权衡内存效率和垃圾回收时间的算法,适用于老年代等生命周期较长的对象区域,因为这些区域的垃圾回收对性能的影响需要最小化。

(图源网络)

分代收集算法是JVM中实现垃圾回收的一种高效策略,它基于这样一个观察:不同年龄的对象具有不同的特性。因此,根据对象的存活时间,JVM的堆内存被划分为几个不同的区域,主要包括年轻代、老年代和永久代。

  1. 年轻代(Young Generation): 年轻代通常用于存放新创建的对象。由于大部分新对象很快就会变得不可达(即变为垃圾),因此年轻代采用复制算法(Copying)。这种算法将内存分为两个或多个相等的部分,当一部分被填满时,就将仍然存活的对象复制到另一部分,并清理掉原区域。这样可以避免内存碎片,并且使得分配新对象的操作更快。

  2. 老年代(Old Generation): 老年代用于存放那些在年轻代中存活下来,即经过一定次数的垃圾回收后仍然存在的对象。这些对象通常有较长的生命周期。老年代采用标记-整理算法(Mark and Compact),该算法在标记出所有存活的对象后,会将这些对象向一端移动,然后直接清理掉端边界以外的内存。这有助于减少内存碎片,提高内存利用率。

  3. 永久代(Permanent Generation)/元空间(Metaspace): 永久代(在Java 8及之前的版本中存在)或元空间(在Java 8及之后的版本中引入)用于存放JVM自己的对象,如类加载器、类信息等。这个区域通常使用标记-清除或标记-整理算法进行垃圾回收。

分代收集算法的优势在于它能够针对不同生命周期的对象采取最合适的垃圾回收策略,从而在管理内存方面达到高效率和性能。现代商业虚拟机,如HotSpot,都采用了分代收集作为其主要的垃圾回收策略,以适应各种应用场景和工作负载。

(图源网络)

相关推荐

  1. JVM垃圾回收算法

    2024-04-05 10:58:05       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-05 10:58:05       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-05 10:58:05       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-05 10:58:05       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-05 10:58:05       20 阅读

热门阅读

  1. Spark面试整理-解释Spark Streaming是什么

    2024-04-05 10:58:05       13 阅读
  2. AcWing 787. 归并排序——算法基础课题解

    2024-04-05 10:58:05       15 阅读
  3. pytorch中的torch.nn.Linear

    2024-04-05 10:58:05       14 阅读
  4. Python爬虫实战-1

    2024-04-05 10:58:05       12 阅读
  5. 设计模式:抽象工厂

    2024-04-05 10:58:05       28 阅读
  6. 飞机降落(c++实现)

    2024-04-05 10:58:05       12 阅读
  7. P1914 小书童——凯撒密码,学会字符串的拆分

    2024-04-05 10:58:05       16 阅读
  8. odoo中创建OWL组件

    2024-04-05 10:58:05       14 阅读
  9. php获取1688拍立淘api

    2024-04-05 10:58:05       15 阅读
  10. UDP和TCP之间的对比

    2024-04-05 10:58:05       12 阅读