Java 中的垃圾回收






4.33/5 (2投票s)
本文介绍了 Java 虚拟机中垃圾回收的工作原理。
引言
当 Java 程序开始执行时,操作系统会为 JVM 分配一些内存。 JVM 使用此内存进行所有操作。 这部分内存称为堆内存。 每当我们通过 'new
' 运算符创建一个对象时,都会从堆内存区域为其分配一些内存。 这个堆内存区域对所有正在运行的线程都是全局可访问的。 如果所有正在运行的线程都从堆内存区域获取内存来创建对象,那么应该有一些机制来回收这些内存,否则堆内存将会耗尽。 在像 C++ 这样的编程语言中,如果有人使用 'new
' 运算符创建一个对象,那么程序员有责任跟踪该对象并在不再需要该对象时通过析构函数清除内存,否则会导致内存泄漏。 但是在 Java 中,我们没有任何显式销毁对象的方法。 JVM 负责处理这种内存管理,并减轻程序员对内存泄漏的担忧。 任何声称对 Java 编程有很好了解的程序员都必须了解垃圾回收在幕后是如何工作的。
垃圾回收是一种回收堆内存的机制,该堆内存被程序中任何活动线程未使用的对象占用。 JVM 定期运行一个名为垃圾收集器的进程,该进程检查哪些对象正在被使用,哪些没有,并从未使用过的对象中回收内存,并将其返回到堆内存以供将来使用。 除了从未使用过的对象中回收内存外,垃圾收集器还确保具有活动引用的对象存在,从而避免了称为悬空引用的情况。
对象何时符合垃圾回收的条件
如果一个对象无法被任何活动线程访问,或者未被任何其他可访问对象引用,则该对象符合垃圾回收的条件。 JVM 假定某些对象(称为根)是可访问的。 然后,它开始从这些根开始跟踪其他可访问对象,并将它们标记为活动对象。 最后,它会清除所有未标记为活动的其他对象。 垃圾收集器会考虑循环依赖。 如果某个对象 A 可以从另一个对象 B 访问,并且对象 B 可以从对象 A 访问,但如果 A 和 B 都无法从任何其他活动对象访问,那么它们都符合垃圾回收的条件。
在以下情况下,对象符合垃圾回收的条件
- 如果对象被赋值为
null
值,例如 'object = null
',那么它就符合垃圾回收的条件。 - 如果父对象 A 包含对象 B 的引用,并且对象 A 被设置为
null
,那么对象 B 也符合垃圾回收的条件。 - 如果对象是在一个块内创建的,并且程序控制超出了该块的范围,那么该对象就符合垃圾回收的条件。
堆内存分代和分代垃圾回收
JVM 将堆内存分成称为代的较小部分。 这些是新生代、老年代和永久代。 它再次将新生代划分为更小的部分,称为 Eden 空间、Survivor 空间 1 和 Survivor 空间 2。

所有新创建的对象首先被放置在新生代中。 如果它们在一定数量的垃圾回收中幸存下来,那么它们将被提升到老年代。 由于大多数对象仅在短时间内存在,并且只有少数对象会随着时间的推移保持分配状态,因此新生代比老年代更频繁地进行垃圾回收。
以下步骤描述了新分配的对象如何在新生代中幸存下来并被提升到老年代。
- 当创建一个新对象时,会从 Eden 空间为其分配内存。
幸存者空间 1 (S1) 和幸存者空间 2 (S2) 都从空分配开始。
- 当 Eden 空间被填满时,JVM 运行一个小的垃圾收集器,它清除未引用的对象并将引用的对象移动到第一个幸存者空间 (S1)。
这里,绿色代表引用的对象,红色代表未引用的对象。 移动到幸存者空间 1 后,引用的对象的引用计数会增加 1。
- 新创建的对象仍然从 Eden 空间分配内存。 在第二次小型垃圾回收后,那些在 Eden 空间和 S1 中都有活动引用的对象被移动到 S2 区域。
- 这样,被引用的对象在幸存者空间之间不断振荡,直到它们达到阈值。
- 一段时间后,如果某些对象在某个阈值限制(在本例中为 5)后仍然存在,它们将被提升到老年代。
通过这种方式,由于小型垃圾回收,对象会继续被提升到老年代。 最后,JVM 在老年代上运行一个主要的垃圾收集器,它回收被未使用对象占用的内存,并将其返回到堆内存。