You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:24:59