bigywater.tech
常见报错 · 46

Java OutOfMemoryError 的 6 种消息:GC overhead limit exceeded 只在 Parallel 下出现,Metaspace 时堆只用了 4 MiB,别的线程 OOM 时进程照样跑

OutOfMemoryError 后面跟的那一句才是诊断。这篇用 JDK 21,在本机把 6 种消息分别复现:每一种撑爆的是堆、元空间、线程还是堆外内存,什么参数能让进程退出或留下堆转储,GC overhead limit 为什么只在 Parallel 下出现,以及 catch 住 OOM 以后怎么才能真的恢复。

10 / 10
同一个程序在 Parallel 收集器下报 GC overhead limit exceeded 的次数;G1 和 Serial 都是 0 / 10
2557
-XX:MaxMetaspaceSize=16m 时创建的代理类数,此时堆只用了约 4 MiB
4074
这台 Mac 上 JVM 能启动的线程数(kern.num_taskthreads = 4096)

Java heap space

实测 · 本机执行输出
  主线程 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。

GC overhead limit

实测 · 本机执行输出
  -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 -2147483648

Requested array size exceeds VM limit 与 NegativeArraySizeException 是不同的问题。

Metaspace

实测 · 本机执行输出
  MaxMetaspaceSize=16m(退出码 0,用时 0.47 s):
      创建了 2557 个代理类后:java.lang.OutOfMemoryError: Metaspace
      此时堆已用约 4 MiB(最大 512 MiB)

类元数据满而堆很空,问题是类不断生成、类加载器不卸载。

native thread

实测 · 本机执行输出
  默认(退出码 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

来自操作系统的线程数限制,与堆无关。

Direct buffer

实测 · 本机执行输出
  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 OOM

实测 · 本机执行输出
  在 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。

分步演示

OutOfMemoryError
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 OpenJDK 21.0.13(Homebrew openjdk@21),每个程序是 javac 编译后 java 直接运行的小 Main.java,堆都用 -Xmx 压得很小;GC overhead limit 的实验每种收集器跑 10 次
    • 没有验证:Linux 容器里 cgroup 内存限制导致的 OOM Killer(内核直接 kill -9,Java 里看不到 OutOfMemoryError);Kubernetes 里 OOMKilled;unable to create native thread 在 Linux 上由 ulimit -u 或 pids 限制触发的样子;G1 在极端情况下是否也会出现 GC overhead limit(这里 10 次全是 Java heap space);其它 JDK 版本(17、27)的消息文字;ZGC / Shenandoah;Compressed class space 这一类 Metaspace 变种;jcmd / MAT 分析 hprof 的过程
    京ICP备2026067502号-1京公网安备11010502063487号