一、JVM 内存分区
1、基本概念
JVM:全称 Java Virtual Machine,即 Java 虚拟机。它是一个运行在计算机上的程序,主要职责是运行 Java 字节码文件。JVM 是一种规范,本身是一个虚拟计算机,直接和操作系统进行交互,不直接与硬件交互。操作系统可以帮助 JVM 完成与硬件的交互工作。Java 借助 JVM 实现了平台无关性,即"Write Once,Run Anywhere(编写一次,到处运行)"的跨平台特性。
堆内存:在 JVM 中,堆(Heap)是内存管理的主要区域,用于存放 Java 对象实例和数组。它是 JVM 启动时创建的一块连续内存空间,并且是所有线程共享的。
JVM 内存区域:Java 堆通常被划分为几个不同的区域,如年轻代(Young Generation)、年老代(Old Generation)和永久代(PermGen,在 Java 8 及以后的版本中被称为元空间 Metaspace)。

2、区域解读
年轻代(Young Generation):用于存放新创建的对象。它通常被进一步划分为 Eden 伊甸园区和两个 Survivor 幸存区(From 和 To),2个幸存区大小相等、地位相同、可互换,通过 Minor GC(新生代垃圾回收)来清理不再使用的对象。
年老代(Old Generation):用于存放经过多次 Minor GC 后仍然存活的对象。这些对象通常是比较稳定的,不会被频繁回收。Major GC(老年代垃圾回收)会清理年老代中不再使用的对象。
永久代/元空间(PermGen/Metaspace):用于存放保存 JVM 自身的类的元数据信息、常量池、存储 JAVA 运行时的环境信息等。在 Java 8 及以后的版本中,永久代被 MetaSpace 元空间所替代。元空间位于本地内存中,而不是堆内存中。关闭 JVM 会释放此区域内存,所以此空间不存在垃圾回收。元空间在物理上不属于heap内存,但逻辑上归属于heap内存。永久代必须指定大小限制,字符串常量 JDK1.7 存放在永久代,1.8 后存放在 heap 中,MetaSpace 可以设置,也可不设置,无上限。
3、默认空间大小比例
默认 JVM 试图分配最大内存的总内存的 1/4,初始化默认总内存为总内存的 1/64,年轻代中 heap 的 1/3,老年代占 2/3。

二、GC 垃圾回收
1、基本概念
在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾,需要进行垃圾回收,从而释放内存空间给其它对象使用。这个时候,JVM 将调用垃圾回收机制来回收内存空间。
堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。
所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续。
2、年轻代回收 Minor GC
第一次GC
起始时,所有新建对象(特大对象直接进入老年代)都出生在 eden,当 eden 满了,启动 GC。这个称为 Young GC 或者 Minor GC。
先标记 eden 存活对象,然后将存活对象复制到 s0(假设本次是 s0,也可以是 s1,它们可以调换),eden 剩余所有空间都清空。第一次 GC 完成。

第二次 GC
继续新建对象,当 eden 再次满了,启动 第二次 GC。
先同时标记 eden 和 s0 中存活对象,然后将存活对象复制到 s1。将 eden 和 s0 清空,第二次 GC 完成。

第三次 GC
继续新建对象,当 eden 满了,启动第三次 GC。
先标记 eden 和 s1 中存活对象,然后将存活对象复制到 s0。将 eden 和 s1 清空,第三次 GC 完成。
以后就重复上面的步骤。

通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。但是,如果一个对象一直存活,它最后就在 from、to 来回复制,如果 from 区中对象复制次数达到阈值(默认15次,CMS 为6次,可通过 java 的选项 -XX:MaxTenuringThreshold=N 定),就直接复制到老年代。
3、老年代回收 Major GC
进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。如果老年代也满了,会触发老年代 GC,称为 Old GC 或者 Major GC。由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法。
由于老年代对象也可以引用新生代对象,所以先进行一次 Minor GC,然后在 Major GC 会提高效率。所以,我们一般认为,回收老年代的时候完成了一次 FullGC。所以,一般情况下,我们可以认为 MajorGC = FullGC。

4、CG 触发条件
Minor GC 触发条件:年轻代空间不足,当 eden 区满了之后触发。
Major GC 触发条件:老年代空间不足,满了之后触发;System.gc()手动调用(不推荐)。
Full GC 触发条件:老年代或元空间完全满了之后触发 。
5、区别对比
对比维度 | Minor GC | Major GC | Full GC |
---|---|---|---|
回收区域 | 仅年轻代(Eden + Survivor) | 仅老年代 | 年轻代 + 老年代 + 元空间(JDK8+) |
回收对象 | 新创建、存活时间短的对象 | 存活时间长、晋升到老年代的对象 | 所有区域的垃圾对象 |
执行速度 | 快(毫秒级) | 慢(秒级) | 最慢(秒级甚至分钟级) |
STW 时间 | 短(对应用影响小) | 长(对应用影响大) | 最长(可能导致应用卡顿) |
执行频率 | 高(新对象创建频繁) | 低(老年代对象存活久) | 极低(应尽量避免) |
触发条件 | 年轻代 Eden 区空间不足,无法分配新对象时触发 | 1. 老年代空间不足,无法容纳 Minor GC 后晋升的对象 2. 老年代占用达到阈值(如 CMS 的默认 92%) 3. 主动调用System.gc() (可能触发) |
1. 老年代完全满,无法容纳新对象 2. 元空间 / 永久代空间不足 3. 主动调用System.gc() 且 JVM 执行 4. 年轻代 + 老年代整体空间不足 |
三、可能发生的 OOM 场景
在 Java 中,OutOfMemoryError(OOM)是最常见的内存异常之一,其发生场景与 JVM 的内存区域划分直接相关。
常见报错:java.lang.OutOfMemoryError: Java heap space
发生区域:Java 堆(Heap),用于存储对象实例。
触发原因:堆中无法再为新对象分配内存,且垃圾回收器(GC)也无法回收出足够空间。
常见场景:
-
内存泄漏:对象引用被长期持有(如静态集合缓存未清理、监听器未移除),导致对象无法被 GC 回收,堆积在堆中。
-
内存溢出:短时间内创建大量对象(如批量处理大集合、无限循环创建对象),超过堆的最大容量(-Xmx限制)。
-
大对象分配:单个对象体积过大(如超大数组),直接超过堆剩余空间。
简单解决方式:
基础参数设置:
-
(-Xms):初始堆大小,建议与最大堆一致(避免动态扩容的性能开销)。
-
(-Xmx):最大堆大小,根据应用特性和物理内存设置(如 8GB 内存的服务器,可设为-Xmx4g,预留一半给系统和其他进程)。
-
示例:java -Xms4g -Xmx4g -jar app.jar
分代内存调整:
堆分为年轻代(Eden+Survivor)和老年代,比例失衡可能导致频繁 GC 或溢出:
-
年轻代过小:新对象频繁进入老年代,导致老年代快速占满(触发 Full GC 甚至溢出)。可通过-Xmn设置年轻代大小(建议为堆的 1/3~1/2)。
-
老年代过小:长期存活对象无法容纳,直接溢出。对于长生命周期对象多的应用(如缓存服务),可适当调大老年代比例(如 G1 收集器通过-XX:NewRatio=2设置老年代:年轻代 = 2:1)。
大对象阈值调整:
单个大对象(如超大数组)可能直接进入老年代,若老年代空间不足会触发溢出。可通过-XX:PretenureSizeThreshold设置大对象阈值(单位字节,默认 0,即由 JVM 自动判断),让超过阈值的对象直接在老年代分配,避免年轻代频繁 GC。