解决 Java 程序崩溃:GC overhead limit exceeded 与 NSSM 启动参数配置优化实战

781 阅读3分钟

在最近的项目部署中,我们遇到了一次 生产环境 Java 服务崩溃 的内存溢出问题。日志中多次出现了如下错误:

image.png

java.lang.OutOfMemoryError: GC overhead limit exceeded

这类问题通常意味着 JVM 在进行垃圾回收时耗费了大量时间,却回收不了多少内存,是典型的 内存不足 + GC无效循环 的信号。

小提示: 内存溢出是“用爆了”,内存泄漏是“忘清了”。
溢出是结果,泄漏可能是根因;合理配置 JVM,定期做 heapdump 和 GC 分析,是保障长期运行稳定性的关键。

本文将总结:

  • 这个错误的含义和成因
  • 如何调整 JVM 参数规避问题
  • 如何使用 NSSM 正确注册 Java 服务(重点)
  • 如何生成和定位 heap dump
  • 最终的服务注册脚本示例

📌 一、问题背景

系统报错日志如下:

java.lang.OutOfMemoryError: GC overhead limit exceeded
ERROR [simpleMessageListenerContainer] - Consumer thread error, thread abort.

伴随还有:

RejectedExecutionException: event executor terminated

这是 Java 在频繁 GC 却无法释放有效内存时触发的自我保护机制。


🧠 二、什么是 GC overhead limit exceeded?

JVM 默认启用 -XX:+UseGCOverheadLimit,含义如下:

当 GC 执行时间超过 98%,但只回收了不到 2% 的堆内存,且持续多次后,JVM 会抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded,防止死循环。


🧪 三、分析与排查

我们首先确认了几个问题:

  • 是否有内存泄漏?(查看 GC 日志或使用 VisualVM 分析)
  • 是否服务启动参数中堆内存配置过小?
  • 是否未设置 HeapDumpOnOutOfMemoryError 导致问题难以复现?

最终我们发现,JVM 启动参数配置顺序错误,导致参数未生效,heap dump 也未生成。


🔧 四、JVM 启动参数正确配置

我们修正了启动参数如下(以 NSSM 注册服务为例):

call "%~dp0\nssm.exe" install MyJavaService "C:\app\java\java.exe" ^
  -Xms512m -Xmx1024m ^
  -Dfile.encoding=UTF-8 ^
  -Djava.security.properties=./resources/conf/custom-java.security ^
  -XX:+UseG1GC ^
  -XX:+HeapDumpOnOutOfMemoryError ^
  -XX:HeapDumpPath=C:\app\log\heapdump.hprof ^
  -jar "C:\app\myapp.jar"

🚫 常见错误:

错误示例(不要这么写):

... -jar %JVMParam% myapp.jar
  • 错误点-jar 后所有参数将作为程序参数传给 main(String[] args),而不是 JVM 参数!
  • 结果-Xmx-XX:+HeapDumpOnOutOfMemoryError 等 JVM 参数会被完全忽略。

📁 五、heap dump 文件在哪?

如果你使用了如下配置:

-XX:HeapDumpPath=./heapdump.hprof

这个路径是 相对当前工作目录 的。对于 NSSM 启动的服务,这通常是:

  • 默认是 C:\Windows\System32
  • 你可以通过 nssm set <ServiceName> AppDirectory C:\MyApp 显式设置

建议使用 绝对路径 避免混淆:

-XX:HeapDumpPath=C:\app\log\heapdump.hprof

并提前确保目录存在:

if not exist "C:\app\log" mkdir "C:\app\log"

🧾 六、完整的服务注册脚本

@echo off

set ConfigFile=%~dp0\service.properties

for /f "eol=# tokens=2 delims==" %%t in ('findstr /i "JarPath" "%ConfigFile%"') do set JarPath=%%t
for /f "eol=# tokens=2 delims==" %%t in ('findstr /i "ServiceName" "%ConfigFile%"') do set ServiceName=%%t
for /f "eol=# tokens=2 delims==" %%t in ('findstr /i "ProcessName" "%ConfigFile%"') do set ProcessName=%%t
for /f "eol=# tokens=2 delims==" %%t in ('findstr /i "JarName" "%ConfigFile%"') do set JarName=%%t
for /f "eol=# tokens=2 delims==" %%t in ('findstr /i "JVMParam" "%ConfigFile%"') do set JVMParam=%%t

if not exist "%JarPath%\log" mkdir "%JarPath%\log"
call %~dp0\UninstallService.bat

copy "%JAVA_HOME%\bin\java.exe" "%JarPath%%ProcessName%"

set JVM_FLAGS=%JVMParam% -Dfile.encoding=utf-8 -Djava.security.properties=./resources/conf/custom-java.security -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%JarPath%\log\heapdump.hprof

call "%~dp0\nssm.exe" install %ServiceName% "%JarPath%%ProcessName%" %JVM_FLAGS% -jar "%JarPath%%JarName%"

✅ 七、总结

项目正确做法
OutOfMemoryError 处理使用 -XX:+HeapDumpOnOutOfMemoryError 抓取快照
参数顺序所有 JVM 参数必须写在 -jar 之前
heap dump 存储位置使用绝对路径 + 提前创建目录
NSSM 服务注册明确指定 AppDirectory、日志路径、服务名称

📌 八、参考参数推荐

-Xms512m -Xmx1024m
-XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=C:\logs\heapdump.hprof
-Dfile.encoding=utf-8
-Djava.security.properties=./resources/conf/custom-java.security

如果你也在用 Java + NSSM 部署服务,建议定期 review 启动参数,确保 JVM 设置真正生效。希望本文对你排查此类问题有所帮助。