自动垃圾回收 (GC) 是使 Java 如此流行的最重要的功能之一。 本文解释了为什么 GC 至关重要。它包括自动和分代GC,Java虚拟机 (JVM) 如何划分堆内存,最后,GC如何在JVM内部工作。
Java内存分配
Java内存分为四个部分
- 堆(Heap):对象实例的内存分配在堆中。 当声明对象时,堆中不会分配任何内存。 而是,在栈中为该对象创建引用。
- 栈(Stack):此部分为方法、局部变量和类实例变量分配内存。
- 代码(Code):字节码驻留在此部分。
- 静态(Static):静态数据和方法放置在此部分。
什么是自动垃圾回收 (GC)?
自动 GC 是一个过程,其中识别堆内存中被引用和未被引用的对象,然后考虑删除未被引用的对象。 术语被引用对象意味着程序的某些部分正在使用这些对象。 未被引用对象当前未被程序使用。
像 C 和 C++ 这样的编程语言需要手动分配和释放内存。 这在 Java 中由 GC 自动处理,尽管您可以使用代码中的 system.gc();
调用手动触发 GC。
GC 的基本步骤是
1. 标记已用和未用对象
在此步骤中,已用和未用对象被分别标记。 这是一个耗时的过程,因为必须扫描内存中的所有对象以确定它们是否正在使用中。

(Jayashree Huttanagoudar, CC BY-SA 4.0)
2. 清除/删除对象
清除和删除有两种变体。
简单删除:仅删除未被引用的对象。 但是,由于可用空间分散在可用内存中,因此为新对象分配内存变得困难。

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

(Jayashree Huttanagoudar, CC BY-SA 4.0)
什么是分代垃圾回收 (GC),为什么需要它?
从清除和删除模型可以看出,一旦对象不断增长,从未使用过的对象中回收内存扫描所有对象变得困难。 一项实验研究表明,程序执行期间创建的大多数对象都是短期的。
短生存期对象的存在可用于提高 GC 的性能。 为此,JVM 将内存划分为不同的代。 接下来,它根据这些内存代对对象进行分类,并相应地执行 GC。 这种方法被称为分代GC。
堆内存代和分代垃圾回收 (GC) 过程
为了提高 GC 标记和清除步骤的性能,JVM 将堆内存划分为三代
- 新生代 (Young Generation)
- 老年代 (Old Generation)
- 永久代 (Permanent Generation)

(Jayashree Huttanagoudar, CC BY-SA 4.0)
以下是每个代的描述及其主要特征。
新生代 (Young Generation)
所有创建的对象都存在于此。 新生代进一步分为
- Eden:所有新创建的对象都在此处分配内存。
- 幸存者空间 (S0 和 S1):在经历一次 GC 后,存活的对象会被移动到其中一个幸存者空间。

(Jayashree Huttanagoudar, CC BY-SA 4.0)
发生在新生代的分代 GC 被称为Minor GC。 所有 Minor GC 周期都是“Stop the World”事件,会导致其他应用程序暂停,直到 GC 周期完成。 这就是为什么 Minor GC 周期更快的原因。
总结:Eden 空间包含所有新创建的对象。 一旦 Eden 满了,就会触发第一个 Minor GC 周期。

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

(Jayashree Huttanagoudar, CC BY-SA 4.0)
S0 中对象的年龄为 1,因为它们经历了一次 Minor GC。 现在 Eden 和 S1 都为空。
清除后,Eden 空间再次被新的存活对象填满。 随着时间的推移,Eden 和 S0 中的一些对象变为死亡(未被引用),并且 Eden 的空间再次满了,从而触发了 Minor GC。

(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 后切换。

(Jayashree Huttanagoudar, CC BY-SA 4.0)
此过程一直持续到其中一个幸存对象的年龄达到某个阈值,此时它会通过称为晋升的过程移动到所谓的旧生代。
此外,-Xmn
标志设置新生代的大小。
老年代(Tenured Generation)
这一代包含已经经历了几次 Minor GC 并老化到达到预期阈值的对象。

(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 如何处理你的变量和类实例。 这种理解使您可以计划和排除代码故障,并理解特定平台固有的潜在局限性。
评论已关闭。