OutOfMemoryError 后面跟的那一句才是诊断。这篇用 JDK 21,在本机把 6 种消息分别复现:每一种撑爆的是堆、元空间、线程还是堆外内存,什么参数能让进程退出或留下堆转储,GC overhead limit 为什么只在 Parallel 下出现,以及 catch 住 OOM 以后怎么才能真的恢复。
主线程 OOM(退出码 1,用时 0.05 s):
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Main.lambda$main$0(Main.java:7)
at Main$$Lambda/0x000000c001000a08.run(Unknown Source)
at Main.main(Main.java:9)
OOM 在 worker 线程里(退出码 0,用时 0.16 s):
main 还活着,继续往下走
main 正常结束
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "worker"
加 -XX:+ExitOnOutOfMemoryError(退出码 3,用时 0.04 s):
Terminating due to java.lang.OutOfMemoryError: Java heap space
加 -XX:+HeapDumpOnOutOfMemoryError(退出码 1,用时 0.05 s):
java.lang.OutOfMemoryError: Java heap space
Dumping heap to <tmp>/heap/java_pid17706.hprof ...
Heap dump file created [36000112 bytes in 0.011 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Main.lambda$main$0(Main.java:7)
…(共 7 行)
生成的 hprof 文件:1 个,大小 34 MiB(-Xmx64m 的堆);用 jcmd/MAT 打开分析最常见;别的线程 OOM 时线程死、进程继续,可用 ExitOnOutOfMemoryError 与 HeapDumpOnOutOfMemoryError。
-Xmx64m Parallel:10 次 -> {'GC overhead limit exceeded': 10}
-Xmx64m G1(默认):10 次 -> {'Java heap space': 10}
-Xmx64m Serial:10 次 -> {'Java heap space': 10}
-Xmx64m Parallel + -XX:-UseGCOverheadLimit:10 次 -> {'Java heap space': 10}Parallel 收集器才有的判定,关掉检查只会变回 Java heap space。
-Xmx64m(退出码 0,用时 0.06 s):
new byte[Integer.MAX_VALUE] -> Requested array size exceeds VM limit
new byte[Integer.MAX_VALUE - 8] -> Java heap space
new long[Integer.MAX_VALUE - 2] -> Java heap space
new long[1 << 28](2 GiB) -> Java heap space
new byte[Integer.MAX_VALUE + 1](溢出成负数) -> NegativeArraySizeException -2147483648Requested array size exceeds VM limit 与 NegativeArraySizeException 是不同的问题。
MaxMetaspaceSize=16m(退出码 0,用时 0.47 s):
创建了 2557 个代理类后:java.lang.OutOfMemoryError: Metaspace
此时堆已用约 4 MiB(最大 512 MiB)类元数据满而堆很空,问题是类不断生成、类加载器不卸载。
默认(退出码 0,用时 0.30 s):
[0.254s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 256k, guardsize: 16k,
[0.254s][warning][os,thread] Failed to start the native thread for java.lang.Thread "Thread-4074"
启动了 4074 个线程后:java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
这台机器的 sysctl kern.num_taskthreads = 4096来自操作系统的线程数限制,与堆无关。
MaxDirectMemorySize=32m(退出码 0,用时 0.62 s):
分配了 32 MiB 堆外内存后:java.lang.OutOfMemoryError: Cannot reserve 1048576 bytes of direct buffer memory (allocated: 33554432, limit: 33554432)
堆已用 1 MiB / 最大 256 MiB
不设 MaxDirectMemorySize,-Xmx64m(退出码 0,用时 0.60 s):
分配了 64 MiB 堆外内存后:java.lang.OutOfMemoryError: Cannot reserve 1048576 bytes of direct buffer memory (allocated: 67108864, limit: 67108864)
堆已用 1 MiB / 最大 64 MiB堆外内存的上限默认等于 -Xmx。
在 catch 里先拼字符串再释放(退出码 1,用时 0.04 s):
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Main.main(Main.java:10)
在 catch 里先释放再记录(退出码 0,用时 0.07 s):
先释放再记录:捕获到 OOM,当时持有 31 MiB:Java heap space
释放后再分配 16 MiB:成功,长度 16777216先释放引用再记录日志,否则 catch 块里会再次 OOM。