不能 catch virtualmachineerror,它表示 jvm 已无法正常运行,捕获后继续执行不可靠;oom 时 system.gc() 无效且有害;应通过内存使用率与 gc 监控提前预警。

VirtualMachineError 能不能 catch?
不能,也不该 try-catch。它不是普通异常,而是 JVM 自身已无法维持正常运行状态的信号——比如 OutOfMemoryError、StackOverflowError、InternalError 都属于 VirtualMachineError 的子类。JVM 规范明确要求:这类错误发生时,线程可能已处于不一致状态,继续执行代码不可靠。
常见错误现象:写了个 try { ... } catch (VirtualMachineError e) { ... },结果照样崩溃,甚至掩盖了真实问题位置。
- 别在业务逻辑里捕获
VirtualMachineError或其任意子类(包括OutOfMemoryError) - 唯一合理场景是:在极少数底层监控/诊断工具中,作为“最后防线”记录日志后立即退出,且不尝试恢复或继续处理
- JDK 8+ 中,
catch (Throwable t)默认会捕获它,容易误以为“兜住了”,实则埋下隐患
OOM 发生时,System.gc() 有用吗?
基本没用,还可能拖慢故障定位。触发 OutOfMemoryError 说明堆(或元空间、直接内存等)已耗尽,GC 已经失败多次,此时显式调用 System.gc() 不仅不会释放新内存,反而会阻塞当前线程、加剧响应延迟。
使用场景错位:有人把它当“急救按钮”,但 JVM 在抛出 OOM 前早已跑过 full GC,甚至触发过 HeapDumpOnOutOfMemoryError。
立即学习“Java免费学习笔记(深入)”;
- 禁用所有生产代码中的
System.gc()调用(可通过-XX:+DisableExplicitGC强制屏蔽) - 真正有效的应对是:提前配置
-XX:+HeapDumpOnOutOfMemoryError+-XX:HeapDumpPath=/path,拿到 dump 后用jhat或 VisualVM 分析对象引用链 - 注意
MetaspaceSize和MaxMetaspaceSize默认值较低,动态类加载多的场景(如 Spring Boot、Groovy 脚本)极易触发OutOfMemoryError: Metaspace
如何提前感知 VirtualMachineError 风险?
靠日志和指标被动等崩溃太晚。得在错误发生前,通过 JVM 内建机制暴露压力信号——关键不是“捕获错误”,而是“识别临界状态”。
性能与兼容性影响:JDK 7 引入的 java.lang.management.MemoryUsage 和 GarbageCollectorMXBean 在所有主流 JDK 上稳定可用,开销极低;而依赖第三方 agent 可能引入 class retransformation 风险。
- 定期轮询
MemoryPoolMXBean.getUsage().getUsed()与getMax(),当使用率持续 >90% 且 GC 频次突增,立刻告警 - 监听
GarbageCollectionNotification,若某次 CMS/G1 GC 后内存回收量 - 避免用
Runtime.getRuntime().freeMemory()判断——它返回的是“当前空闲”,不是“可分配”,且受 GC 策略影响极大
崩溃后保留现场的关键配置
很多团队重启服务太快,丢掉了唯一能定位根因的线索。JVM 崩溃不是黑盒,但默认行为对排障极不友好。
容易踩的坑:只配了 -XX:+HeapDumpOnOutOfMemoryError,却没设 -XX:ErrorFile 或忽略 core 文件权限问题,导致崩溃时既没堆 dump,也没 hs_err 日志。
- 必须加:
-XX:ErrorFile=/var/log/java/hs_err_%p.log(%p 是进程 PID,防覆盖) - Linux 下确保 JVM 进程对目标目录有写权限,否则
hs_err文件静默失败 - 容器环境要额外挂载
/tmp或日志目录,并配-XX:+PrintGCDetails -XX:+PrintGCTimeStamps到 stdout,方便采集 -
OutOfMemoryError: Direct buffer memory需单独监控java.nio.Bits.reservedMemory(需反射读取),标准 JMX 不暴露
最常被忽略的一点:JVM 在崩溃前可能已无法写文件,所以 hs_err 日志和 heap dump 必须落在同一块高可靠磁盘上,且预留足够空间——别让它和应用日志挤在 /var/log 下,更别放在 tmpfs 里。










