在最近的项目部署中,我们遇到了一次 生产环境 Java 服务崩溃 的内存溢出问题。日志中多次出现了如下错误:
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 设置真正生效。希望本文对你排查此类问题有所帮助。