Java虚拟机内部垃圾回收机制

理解Java如何处理内存并非总是必要的,但它可以帮助你想象JVM如何处理你的变量和类实例。
6 位读者喜欢这篇文章。
Coffee beans

Pixabay. CC0.

自动垃圾回收 (GC) 是使 Java 如此流行的最重要的功能之一。 本文解释了为什么 GC 至关重要。它包括自动和分代GC,Java虚拟机 (JVM) 如何划分堆内存,最后,GC如何在JVM内部工作。

Java内存分配

Java内存分为四个部分

  1. 堆(Heap):对象实例的内存分配在堆中。 当声明对象时,堆中不会分配任何内存。 而是,在栈中为该对象创建引用。
  2. 栈(Stack):此部分为方法、局部变量和类实例变量分配内存。
  3. 代码(Code):字节码驻留在此部分。
  4. 静态(Static):静态数据和方法放置在此部分。

什么是自动垃圾回收 (GC)?

自动 GC 是一个过程,其中识别堆内存中被引用和未被引用的对象,然后考虑删除未被引用的对象。 术语被引用对象意味着程序的某些部分正在使用这些对象。 未被引用对象当前未被程序使用。

像 C 和 C++ 这样的编程语言需要手动分配和释放内存。 这在 Java 中由 GC 自动处理,尽管您可以使用代码中的 system.gc(); 调用手动触发 GC。

GC 的基本步骤是

1. 标记已用和未用对象

在此步骤中,已用和未用对象被分别标记。 这是一个耗时的过程,因为必须扫描内存中的所有对象以确定它们是否正在使用中。

Marking used and unused objects

(Jayashree Huttanagoudar, CC BY-SA 4.0)

2. 清除/删除对象

清除和删除有两种变体。

简单删除:仅删除未被引用的对象。 但是,由于可用空间分散在可用内存中,因此为新对象分配内存变得困难。

Normal deleting process

(Jayashree Huttanagoudar, CC BY-SA 4.0)

删除和压缩:除了删除未被引用的对象外,还会压缩被引用的对象。 为新对象分配内存相对容易,并且内存分配性能得到提高。

Deletion with compacting

(Jayashree Huttanagoudar, CC BY-SA 4.0)

什么是分代垃圾回收 (GC),为什么需要它?

从清除和删除模型可以看出,一旦对象不断增长,从未使用过的对象中回收内存扫描所有对象变得困难。 一项实验研究表明,程序执行期间创建的大多数对象都是短期的。

短生存期对象的存在可用于提高 GC 的性能。 为此,JVM 将内存划分为不同的代。 接下来,它根据这些内存代对对象进行分类,并相应地执行 GC。 这种方法被称为分代GC

堆内存代和分代垃圾回收 (GC) 过程

为了提高 GC 标记和清除步骤的性能,JVM 将堆内存划分为三代

  • 新生代 (Young Generation)
  • 老年代 (Old Generation)
  • 永久代 (Permanent Generation)
Hotspot heap structure

(Jayashree Huttanagoudar, CC BY-SA 4.0)

以下是每个代的描述及其主要特征。

新生代 (Young Generation)

所有创建的对象都存在于此。 新生代进一步分为

  1. Eden:所有新创建的对象都在此处分配内存。
  2. 幸存者空间 (S0 和 S1):在经历一次 GC 后,存活的对象会被移动到其中一个幸存者空间。
Object allocation

(Jayashree Huttanagoudar, CC BY-SA 4.0)

发生在新生代的分代 GC 被称为Minor GC。 所有 Minor GC 周期都是“Stop the World”事件,会导致其他应用程序暂停,直到 GC 周期完成。 这就是为什么 Minor GC 周期更快的原因。

总结:Eden 空间包含所有新创建的对象。 一旦 Eden 满了,就会触发第一个 Minor GC 周期。

Filling Eden space

(Jayashree Huttanagoudar, CC BY-SA 4.0)

Minor GC:在此周期中标记存活和死亡对象。 存活对象被移动到幸存者空间 S0。 一旦所有存活对象都移动到 S0,未被引用的对象将被删除。

Copying referenced objects

(Jayashree Huttanagoudar, CC BY-SA 4.0)

S0 中对象的年龄为 1,因为它们经历了一次 Minor GC。 现在 Eden 和 S1 都为空。

清除后,Eden 空间再次被新的存活对象填满。 随着时间的推移,Eden 和 S0 中的一些对象变为死亡(未被引用),并且 Eden 的空间再次满了,从而触发了 Minor GC。

Object aging

(Jayashree Huttanagoudar, CC BY-SA 4.0)

这次标记 Eden 和 S0 中的死亡和存活对象。 来自 Eden 的存活对象被移动到 S1,年龄递增 1。 来自 S0 的存活对象也被移动到 S1,年龄递增 2(因为它们现在经历了两次 Minor GC)。 此时,S0 和 Eden 为空。 每次 Minor GC 后,Eden 和其中一个幸存者空间都为空。

在 Eden 中创建新对象的相同周期继续。 当发生下一个 Minor GC 时,通过将老化对象移动到 S0 来清除 Eden 和 S1。 幸存者空间在每次 Minor GC 后切换。

Additional aging

(Jayashree Huttanagoudar, CC BY-SA 4.0)

此过程一直持续到其中一个幸存对象的年龄达到某个阈值,此时它会通过称为晋升的过程移动到所谓的旧生代。

此外,-Xmn 标志设置新生代的大小。

老年代(Tenured Generation)

这一代包含已经经历了几次 Minor GC 并老化到达到预期阈值的对象。

Promotion

(Jayashree Huttanagoudar, CC BY-SA 4.0)

在上面的示例图中,阈值为 8。 老年代中的 GC 称为Major GC。 使用标志 -Xms-Xmx 设置堆内存的初始大小和最大大小。

永久代 (Permanent Generation)

永久代空间存储与应用程序、J2SE 的库类和方法相关的元数据,以及 JVM 本身正在使用的内容。 JVM 在运行时根据正在使用的类和方法填充此数据。 一旦 JVM 找到未使用的类,它们将被卸载或收集,从而为已使用的类腾出空间。

使用标志 -XX:PermGen-XX:MaxPermGen 设置永久代的初始大小和最大大小。

元空间 (Metaspace)

元空间是在 Java 8u 中引入的,取代了 PermGen。 这样做的优点是自动调整大小,从而避免 OutOfMemory 错误。

总结

本文讨论了 JVM 的各种内存代,以及它们如何有助于自动分代垃圾回收 (GC)。 理解 Java 如何处理内存并非总是必要的,但它可以帮助你想象 JVM 如何处理你的变量和类实例。 这种理解使您可以计划和排除代码故障,并理解特定平台固有的潜在局限性。

下一步阅读什么
标签
User profile image.
Jayashree Huttanagoudar 是 Red Hat India Pvt ltd 的高级软件工程师。 她在 Middleware OpenJDK 团队工作。 她总是渴望学习新事物,这有助于她的工作。

评论已关闭。

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.