Java内存溢出由JVM抛出java.lang.OutOfMemoryError,系统OOM则是内核OOM Killer发送SIGKILL导致进程猝死;前者有异常栈和堆转储,后者无Java异常、仅dmesg可见内核日志。

看错误日志里抛出的是 java.lang.OutOfMemoryError 还是系统级信号
Java 应用崩溃时,真正关键的区分点不在 JVM 参数或堆配置,而在**谁在抛异常、谁在发信号**。
如果日志里明确出现 java.lang.OutOfMemoryError: Java heap space、Metaspace 或 Direct buffer memory 这类信息,说明是 JVM 主动检测到某块内存区域(堆、元空间、直接内存)耗尽,自己抛出的 OutOfMemoryError —— 这属于 Java 内存溢出,JVM 仍可控,能触发 -XX:+HeapDumpOnOutOfMemoryError 等机制。
而系统 OOM 是 Linux 内核的 OOM Killer 在进程整体内存(RSS + Swap + 其他匿名页)超出物理内存+交换空间上限时,强制向进程发送 SIGKILL (signal 9)。此时 JVM 进程会突然消失,没有任何 Java 异常栈,连日志都可能戛然而止,dmesg -T | grep -i "killed process" 才能看到内核日志,例如:
Oct 13 18:22:41 server kernel: Out of memory: Killed process 12345 (java) total-vm:8245678kB, anon-rss:7123456kB, file-rss:0kB
jstat 和 pmap 能暴露 RSS 与堆配置的严重错位
Java 堆设了 -Xmx4g,但 pmap -x 12345 | tail -1 显示 mapped 或 rss 高达 12G,基本可断定不是堆的问题,而是:
立即学习“Java免费学习笔记(深入)”;
- 大量
ByteBuffer.allocateDirect()分配未释放,占用了直接内存(不受-Xmx管) - JNI 代码在 native 层 malloc 了大块内存,JVM 完全不可见
- Log4j2 的
AsyncLogger背后用的 LMAX Disruptor 环形缓冲区,在高吞吐下吃掉数 GB RSS - 容器环境下未配
--memory限制,JVM 自动读取宿主机内存设堆,但实际被 cgroup 限死了 RSS 上限
这时 jstat -gc 12345 可能显示老年代才用 2G,GC 正常,但 top 里 java 进程 RES 已飙到 10G+ —— 这种「堆小 RSS 大」的撕裂感,就是系统 OOM 的前兆。
容器环境必须检查 cgroup memory.max 和 memory.oom_control
在 Docker/K8s 里,OpenJDK 不会自动感知 cgroup v2 的内存限制,-Xmx 仍按宿主机内存算,极易导致 JVM 认为自己还有空间可分,而内核早已盯上它。
验证方式:
-
cat /sys/fs/cgroup/memory.max(cgroup v2)或cat /sys/fs/cgroup/memory/memory.limit_in_bytes(v1),看是否远小于 JVM 的 RSS -
cat /sys/fs/cgroup/memory/memory.oom_control,若oom_kill_disable 0且under_oom 1,说明已被 OOM Killer 标记 - JVM 启动加
-XX:+UseContainerSupport(JDK8u191+/JDK10+ 默认开启),并配合-XX:MaxRAMPercentage=75.0替代硬编码-Xmx
没开容器支持时,JVM 对 cgroup 一无所知,jcmd 12345 VM.native_memory summary 里的 committed 也远低于 top 的 RES,这种偏差就是危险信号。
系统 OOM 发生后,hs_err_pid*.log 里没有 java.lang.OutOfMemoryError
Java 内存溢出一定会在 crash log(hs_err_*.log)的 Current thread 或 Stack Trace 区域留下 java.lang.OutOfMemoryError 字样,甚至附带线程栈和 heap dump 路径。
但系统 OOM 导致的 kill,hs_err 文件可能根本不会生成,或者只写到「# JRE version: …」就中断;即使有,里面也找不到任何 Java 异常痕迹,只有类似这样的线索:
siginfo: si_signo: 9 (SIGKILL), si_code: -1 (SI_KERNEL)-
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)下面全是空的或只有V [libjvm.so+0x...] - 无
Java frames段,或只有极短的初始化帧
这时候别翻 GC 日志,直接去 dmesg 和容器 runtime 日志(如 crictl logs)找内核杀进程记录 —— 这才是第一现场。
真正容易被忽略的是:系统 OOM 和 Java OOM 可能共存于同一进程生命周期里。比如先因 Direct Memory 泄漏把 RSS 拉高,触发 OOM Killer;但在此之前,应用已因堆内存泄漏频繁 Full GC,日志里混着两种线索。必须用 dmesg 和 jstat 交叉比对时间戳,才能分清因果。


















