使用JDI调试Java程序无有效事件,目标VM启动即终止求助
JDI调试程序无法触发断点及步调试事件问题解决
问题描述
基于Baeldung的JDI教程实现了两个Java类:JDIExampleDebuggee(被调试程序)和JDIExampleDebugger(调试程序),但运行调试程序时,仅输出VMStartEventImpl、VMDeathEventImpl、VMDisconnectEventImpl事件,目标VM启动后立即终止,无法触发断点、步调试等有效事件,多次排查未找到问题。
被调试程序代码(JDIExampleDebuggee)
package com.linuxea; public class JDIExampleDebuggee { public static void main(String[] args) { String jpda = "Java Platform Debugger Architecture"; System.out.println("Hi Everyone, Welcome to " + jpda); // 此处设置断点 String jdi = "Java Debug Interface"; // 此处设置断点并尝试步调试 String text = "Today, we'll dive into " + jdi; System.out.println(text); } }
调试程序代码(JDIExampleDebugger)
package com.linuxea; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.Bootstrap; import com.sun.jdi.ClassType; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.StackFrame; import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector.Argument; import com.sun.jdi.connect.LaunchingConnector; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.LocatableEvent; import com.sun.jdi.event.StepEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.ClassPrepareRequest; import com.sun.jdi.request.StepRequest; import java.util.Map; public class JDIExampleDebugger<T> { private Class<T> debugClass; private int[] breakPointLines; public static void main(String[] args) { JDIExampleDebugger<JDIExampleDebuggee> debuggerInstance = new JDIExampleDebugger<>(); debuggerInstance.debugClass = JDIExampleDebuggee.class; debuggerInstance.breakPointLines = new int[]{6}; VirtualMachine vm; try { vm = debuggerInstance.connectAndLaunchVM(); debuggerInstance.enableClassPrepareRequest(vm); EventSet eventSet; while ((eventSet = vm.eventQueue().remove()) != null) { for (Event event : eventSet) { System.out.println(event.getClass().getName()); if (event instanceof ClassPrepareEvent) { debuggerInstance.setBreakPoints(vm, (ClassPrepareEvent) event); } if (event instanceof BreakpointEvent) { debuggerInstance.displayVariables((BreakpointEvent) event); } if (event instanceof BreakpointEvent) { debuggerInstance.enableStepRequest(vm, (BreakpointEvent) event); } if (event instanceof StepEvent) { debuggerInstance.displayVariables((StepEvent) event); } vm.resume(); } } } catch (VMDisconnectedException e) { System.out.println("Virtual Machine is disconnected."); throw new RuntimeException(e); } catch (Exception e) { e.printStackTrace(); } } public void enableStepRequest(VirtualMachine vm, BreakpointEvent event) { // 为最后一个断点启用步调试请求 if (event.location().toString(). contains(debugClass.getName() + ":" + breakPointLines[breakPointLines.length - 1])) { StepRequest stepRequest = vm.eventRequestManager() .createStepRequest(event.thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable(); } } public VirtualMachine connectAndLaunchVM() throws Exception { LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager() .defaultConnector(); Map<String, Argument> arguments = launchingConnector.defaultArguments(); arguments.get("main").setValue(debugClass.getName()); return launchingConnector.launch(arguments); } public void enableClassPrepareRequest(VirtualMachine vm) { ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest(); classPrepareRequest.addClassFilter(debugClass.getName()); classPrepareRequest.enable(); } public void setBreakPoints(VirtualMachine vm, ClassPrepareEvent event) throws AbsentInformationException { ClassType classType = (ClassType) event.referenceType(); for (int lineNumber : breakPointLines) { Location location = classType.locationsOfLine(lineNumber).get(0); BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location); bpReq.enable(); } } public void displayVariables(LocatableEvent event) throws IncompatibleThreadStateException, AbsentInformationException { StackFrame stackFrame = event.thread().frame(0); if (stackFrame.location().toString().contains(debugClass.getName())) { Map<LocalVariable, Value> visibleVariables = stackFrame .getValues(stackFrame.visibleVariables()); System.out.println("Variables at " + stackFrame.location().toString() + " > "); for (Map.Entry<LocalVariable, Value> entry : visibleVariables.entrySet()) { System.out.println(entry.getKey().name() + " = " + entry.getValue()); } } } }
程序运行输出
com.sun.tools.jdi.EventSetImpl$VMStartEventImpl com.sun.tools.jdi.EventSetImpl$VMDeathEventImpl com.sun.tools.jdi.EventSetImpl$VMDisconnectEventImpl Virtual Machine is disconnected. Exception in thread "main" java.lang.RuntimeException: com.sun.jdi.VMDisconnectedException at com.linuxea.JDIExampleDebugger.main(JDIExampleDebugger.java:64) Caused by: com.sun.jdi.VMDisconnectedException at com.sun.tools.jdi.EventQueueImpl.removeUnfiltered(EventQueueImpl.java:199) at com.sun.tools.jdi.EventQueueImpl.remove(EventQueueImpl.java:96) at com.sun.tools.jdi.EventQueueImpl.remove(EventQueueImpl.java:82) at com.linuxea.JDIExampleDebugger.main(JDIExampleDebugger.java:41) Process finished with exit code 1
问题根源及修复方案
1. 目标VM启动后未暂停,代码直接执行完毕
通过LaunchingConnector启动目标VM时,默认会立即执行代码,此时调试程序还未完成断点设置,目标VM已经运行结束。
修复代码:
修改connectAndLaunchVM方法,添加启动暂停参数:
public VirtualMachine connectAndLaunchVM() throws Exception { LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager() .defaultConnector(); Map<String, Argument> arguments = launchingConnector.defaultArguments(); arguments.get("main").setValue(debugClass.getName()); // 配置目标VM启动时暂停,等待调试程序初始化 Argument optionsArg = arguments.get("options"); optionsArg.setValue(optionsArg.value() + " -agentlib:jdwp=transport=dt_shmem,server=y,suspend=y"); return launchingConnector.launch(arguments); }
2. VMStartEvent处理时过早调用vm.resume()
原代码对所有事件都调用vm.resume(),包括VMStartEvent,此时断点还未设置完成,恢复VM会导致代码直接运行。
修复代码:
修改主循环的事件处理逻辑,仅在必要时恢复VM:
while ((eventSet = vm.eventQueue().remove()) != null) { for (Event event : eventSet) { System.out.println(event.getClass().getName()); boolean shouldResume = true; if (event instanceof ClassPrepareEvent) { debuggerInstance.setBreakPoints(vm, (ClassPrepareEvent) event); } else if (event instanceof BreakpointEvent) { debuggerInstance.displayVariables((BreakpointEvent) event); debuggerInstance.enableStepRequest(vm, (BreakpointEvent) event); } else if (event instanceof StepEvent) { debuggerInstance.displayVariables((StepEvent) event); } else if (event instanceof com.sun.jdi.event.VMStartEvent) { // VM启动事件暂不恢复,等待断点设置完成 shouldResume = false; } if (shouldResume) { vm.resume(); } } }
3. 断点位置判断依赖字符串格式,易出错
原代码通过location().toString()判断断点位置,依赖字符串格式稳定性,改用直接比较类名和行号更可靠。
修复代码:
修改enableStepRequest方法:
public void enableStepRequest(VirtualMachine vm, BreakpointEvent event) { Location location = event.location(); // 直接比较类名和行号 if (location.declaringType().name().equals(debugClass.getName()) && location.lineNumber() == breakPointLines[breakPointLines.length - 1]) { StepRequest stepRequest = vm.eventRequestManager() .createStepRequest(event.thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER); stepRequest.enable(); } }
4. 确保被调试程序保留调试信息
被调试程序编译时必须生成调试信息(-g参数),否则JDI无法获取行号等信息,导致断点无法设置。
验证方式:
- IDE中确保开启调试信息生成(一般默认开启)
- 命令行编译时执行:
javac -g com/linuxea/JDIExampleDebuggee.java
内容的提问来源于stack exchange,提问作者Linuxea
相关产品推荐
相关产品推荐

