本文介绍如何在 java 应用不重启、不修改启动参数的前提下,通过 jvm 内置诊断命令(diagnosticcommandmbean)动态开启/关闭垃圾回收(gc)日志,适用于生产环境紧急排查场景。
本文介绍如何在 java 应用不重启、不修改启动参数的前提下,通过 jvm 内置诊断命令(diagnosticcommandmbean)动态开启/关闭垃圾回收(gc)日志,适用于生产环境紧急排查场景。
在 Java 性能调优与故障诊断中,GC 日志是分析内存分配模式、识别频繁 Full GC 或内存泄漏的关键依据。传统方式需在 JVM 启动时添加如 -Xlog:gc*:file=gc.log(JDK 9+)或 -XX:+PrintGCDetails -Xloggc:gc.log(旧版)等参数——但这要求应用重启,对线上服务极不友好。幸运的是,自 JDK 7u40 起,HotSpot JVM 提供了 运行时可调用的诊断命令接口(Diagnostic Commands, JCMD),配合 DiagnosticCommandMBean,可在程序运行中即时启用或禁用 GC 日志,真正实现“按需诊断”。
✅ 核心原理:JVM 的动态诊断能力
HotSpot 将诊断功能封装为 MBean(com.sun.management:type=DiagnosticCommand),支持通过 JMX 远程或本地调用。其中 vmLog 命令等效于 jcmd <pid> VM.log,可动态配置日志输出目标与内容范围(如 what=gc 表示仅记录 GC 事件)。
? 示例一:启用 GC 日志并输出到标准输出
以下代码在 JVM 运行中立即激活 GC 日志,并将日志打印至控制台(适合开发调试):
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
import javax.management.JMException;
public class GcLogExample {
public static void main(String[] args) throws JMException {
// 启用 GC 日志(输出到 stdout)
String[] command = { "what=gc" };
String result = (String) ManagementFactory.getPlatformMBeanServer().invoke(
ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"),
"vmLog",
new Object[]{command},
new String[]{command.getClass().getName()}
);
if (!result.isBlank()) {
System.out.println("[JCMD Response] " + result);
}
// 触发几次 GC(强制分配大对象)
for (int i = 0; i < 300; i++) {
int[] obj = new int[10_000_000]; // ~40MB 每次
}
}
}✅ 输出效果:控制台将实时打印类似 [2024-06-15T14:22:31.123+0800][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 234M->28M(512M) 12.345ms 的结构化日志。
立即学习“Java免费学习笔记(深入)”;
? 示例二:写入临时文件(推荐生产使用)
为避免干扰标准输出,更稳妥的方式是将日志定向至独立文件:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
import javax.management.JMException;
public class GcLogToFileExample {
public static void main(String[] args) throws JMException, IOException {
// 创建临时日志文件
Path logPath = Files.createTempFile("gc-runtime-", ".log");
System.out.println("✅ GC 日志将写入: " + logPath.toAbsolutePath());
// 配置 vmLog 命令:输出到文件 + 仅 GC 事件
String[] command = { "output=" + logPath, "what=gc" };
String result = (String) ManagementFactory.getPlatformMBeanServer().invoke(
ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"),
"vmLog",
new Object[]{command},
new String[]{command.getClass().getName()}
);
System.out.println("[JCMD Response] " + result);
// 模拟内存压力
for (int i = 0; i < 300; i++) {
new byte[10_000_000];
}
}
}⚠️ 关键注意事项
- JDK 版本要求:需 JDK 7u40+(推荐 JDK 11+),且必须使用 HotSpot JVM(OpenJ9 不兼容)。
- 权限限制:运行时需具备 ManagementPermission("control"),若启用安全管理器(SecurityManager),需提前授权。
- 线程安全:vmLog 命令是线程安全的,但多次调用会覆盖前一次配置(非追加)。
- 关闭日志:执行 what=gc=off 即可停用(示例:new String[]{"what=gc=off"})。
- 替代方案:也可直接使用命令行工具 jcmd <pid> VM.log what=gc output=/tmp/gc.log,无需编码,适合运维快速介入。
? 总结
动态 GC 日志能力打破了“必须重启才能诊断”的固有约束,是 Java 生产环境可观测性的重要补充。它不依赖外部代理(如 JFR Agent),零侵入、低开销,配合自动化脚本或监控平台,可构建响应式性能诊断闭环。记住:诊断不是越早越好,而是恰逢其时——而 vmLog 正赋予你这个时机。










