Java Heap

17 Mar 2025 | 5 分钟阅读

在 Java 中,堆是所有线程共享的一块内存区域。堆中会分配所有类的实例和数组。它在 JVM 启动时创建。一个自动存储管理系统会回收堆。它的大小可以是固定的,也可以是可变的。它不需要是连续的。

堆结构

Java Heap

堆分为两个部分(或代)

  • 年轻代(或年轻代空间或新生代)
  • 老年代(或老年代空间或永久代)

年轻代

它是堆的一部分。它保留用于分配对象。当它填满时,Minor GC 会从 Eden 收集垃圾到其中一个幸存者空间。在其中一个幸存者空间中可用于垃圾回收的对象被清除,其余对象移至另一个幸存者空间。因此,总有一个空的幸存者空间。所有在年轻代中存活时间较长的对象都会被转移到老年代。

Java 近期版本中,新生代的一部分称为保留区。它包含新生代中最近分配的对象。它不会收集垃圾,直到下一个年轻代。

  • Eden:所有对象首先在此创建。它比两个幸存者空间都大。它占年轻代空间的 76%。当 Eden 填满时,会触发 Minor GC。
  • 幸存者空间:这是一个对象池,其中包含已在 Eden 空间垃圾回收中幸存下来的对象。有两个幸存者空间,称为幸存者from (S0) 和幸存者to (S1)。它避免了内存碎片。关于幸存者的重要一点是,两个幸存者空间之一总是空的。

年轻代(Eden 空间 + 幸存者from 空间)中的所有活动对象都会被移至幸存者to 空间。完成此过程后,幸存者from 空间为空。在幸存者空间之间复制活动对象的过程会重复几次,直到一些对象被认为已经成熟且足够老。这样的对象可以移至老年代,而不是移至另一个幸存者空间。为了确定对象是否准备好移至老年代,GC 会计算对象幸存的总回收次数。如果对象未被回收,则其生命周期会增加。

老年代空间

它包含生命周期较长的对象。这个过程称为老年代回收。长生命周期的对象在多次 Minor GC 后幸存下来。当老年代空间接近其极限时,大部分老年代内存会被清除。通常,当老年代空间达到其极限时,垃圾收集器会被执行。老年代垃圾收集器称为 Major GC。它需要很长时间来移除对象。

这里有一个问题:“如果老年代中的对象需要引用年轻代中的对象,会发生什么?”

为了处理这类情况,JVM 在老年代中维护一个称为卡片表(card table)的表。它是一个 512 字节的内存块。当老年代中的对象引用年轻代中的对象时,它会记录在卡片表中。当 Minor GC 为年轻代执行时,GC 只会搜索此表。它会确定该对象是否应被 GC,而不是检查老年代中所有对象的引用。写屏障(write barrier)管理卡片表。它是一种能够提高 Minor GC 性能的机制。

非堆内存

它包括一个所有线程共享的方法区。它存储每个类的结构。它的大小可以是固定的,也可以是可变的。它不需要是连续的。

永久代(PermGen)

JVM 在运行时生成它。它包含 JVM 所需的应用程序元数据。元数据包括应用程序使用的类和方法。它还包括 Java SE 库类和方法。

在 Java 8 中,Metaspace 取代了它。这意味着在 Java 8 中不会出现 java.lang.OutOfMemoryError。Metaspace 中引入了两个新标志:-XXMetasapceSize?XXMaxMetaspaceSize。Metaspace 的主要目的是,只要类加载器存在,元数据就在 Metaspace 中存在。

PermGen 和 Metaspace 的主要区别在于:PermGen 是堆的一部分,而 Metaspace 是本地内存的一部分。

Java Heap 2

代码缓存 (Code Cache):它是一个独立于堆的内存区域。它用于编译和存储本地代码。它是一个固定大小的空间。如果它填满,JVM 将不会编译任何其他代码。为了避免这种情况,您可以使用以下大小选项来调整代码缓存:

  • InitialCodeCacheSize:其默认大小(字节)为 160K。
  • ReservedCacheSize:其默认最大大小为 48M。
  • CodeCacheExpansionSize:其默认大小为 32K 或 64K。

JVM 使用 Use Code Cache Flushing 选项来控制代码缓存区域的刷新。其默认值为 false。即时编译器(JIT)是代码缓存区域的最大用户。

java.lang.OutOfMemoryError 的原因

当应用程序尝试向堆空间区域添加更多数据但没有足够空间时,会触发此错误。每当达到堆大小限制时,JVM 就会抛出 Java 堆空间错误。

  • 数据量激增:当应用程序尝试向堆空间添加更多数据但没有足够空间时,就会发生这种情况。
  • 内存泄漏:这是一个编程错误,导致应用程序不断消耗更多内存。它可能以多种方式发生。当应用程序不再使用对象,但垃圾收集器无法识别它们时,就会出现这种情况。您可以使用内存管理工具(如 HP jmeter、JProbe 和 IBM Tivoli)来识别无用对象和内存泄漏。
Java Heap 3

示例

输出

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemoryLeaksDemo.main(MemoryLeaksDemo.java:5)

下一主题