JVM 内存深度分析

堆内存

在 Java 程序执行过程中,Java 虚拟机将其管理的主内存分为多个区域,每个区域存储不同类型的数据。最大的一部分内存空间称为堆(Heap),它是 Java 虚拟机的运行时数据区域,所有的实例和数组都存储在这里。

堆在 Java 虚拟机启动时创建,由所有线程共享,同时也是垃圾收集器的主要工作区域,因此这一部分区域也称为 堆内存GC 堆。当垃圾收集器回收堆中的数据时,会扫描并清理使用 new 关键字创建的无用对象,以释放内存并避免内存资源的浪费。

为了提高垃圾收集效率,堆内存进一步划分为年轻代、老年代和永久代。

JVM 堆

  • 年轻代:包括伊甸区、SurvivorFrom 区和 SurvivorTo 区,主要用于存储新创建的对象。大多数新对象在伊甸区分配(如果对象太大,则直接分配到老年代),年轻代的垃圾收集过程称为 Minor GC。当伊甸区内存不足时,会触发 Minor GC。

    在 Minor GC 开始之前,对象仅存在于伊甸区和 SurvivorFrom 区;在 Minor GC 期间,来自伊甸区和 SurvivorFrom 区的幸存对象被移动到 SurvivorTo 区,它们的年龄增加 1,而伊甸区和 SurvivorFrom 区被清理;在 Minor GC 之后,SurvivorFrom 区和 SurvivorTo 区的功能进行交换,在下一个 Minor GC 中,来自 SurvivorTo 区和伊甸区的幸存对象被移动到 SurvivorFrom 区,并计算对象的年龄。当一个对象的年龄达到 15 时,它会被分配到老年代。

  • 老年代:也称为老年代区,用于存储从年轻代幸存下来的对象。老年代区的垃圾收集过程称为 Major GC。老年代存储的是更稳定的对象,通常不进行频繁的 Major GC。只有在新对象进入老年代导致空间不足,或者程序无法找到足够大的连续空间为新创建的大对象分配内存时,才会触发 Major GC。

    由于需要扫描和回收,Major GC 会耗时较长。Major GC 会产生内存碎片,当老年代也没有足够的内存为进入的对象分配时,会抛出 OOM(内存溢出)异常。

  • 永久代:也称为永久存储区,主要存储类和元(元数据)信息。在 Java 8 中,永久代被移除,并由元空间(Metaspace)取代。元空间不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

非堆内存

非堆内存指的是 Java 虚拟机堆外管理的内存区域,在 Java 虚拟机堆内存之外的内存区域中分配一些对象实例,直接由操作系统管理(而非虚拟机),包括代码缓存区域、元空间/永久空间。

区域说明见下表。

非堆内存区域描述
代码缓存用于编译和存储本地代码的区域。
永久空间用于存储虚拟机的静态数据的区域,例如类和方法对象。
元空间元空间,用于在本地内存中存储类的元数据的区域。
直接缓冲区直接缓冲区区域。