注册事件
分析器和目标程序之间采用基于事件的模式进行通信。分析器向虚拟机实例注册所关注的事件。事件发生时,虚拟机将相关事件信息放入事件队列中,采用 生产者 - 消费者 的模式与分析器同步。
注册事件
EventRequestManager 管理事件请求,它支持创建、删除和查询事件请求。EventRequest 支持三种挂起策略:
EventRequest.SUSPEND_ALL : 事件发生时,挂起所有线程
EventRequest.SUSPEND_EVENT_THREAD : 事件发生时,挂起事件源线程
EventRequest.SUSPEND_NONE : 事件发生时,不挂起任何线程
JDI 支持多种类型的 EventRequest,如 ExceptionRequest,MethodEntryRequest,MethodExitRequest,ThreadStartRequest 等,可以参考 文档。
清单 4. 注册事件
EventRequestManager mgr = vm.eventRequestManager(); // 注册异常事件 ExceptionRequest excReq = mgr.createExceptionRequest(null, true, true); excReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); excReq.enable(); // 注册进方法事件 MethodEntryRequest menr = mgr.createMethodEntryRequest(); menr.setSuspendPolicy(EventRequest.SUSPEND_NONE); menr.enable(); // 注册出方法事件 MethodExitRequest mexr = mgr.createMethodExitRequest(); mexr.setSuspendPolicy(EventRequest.SUSPEND_NONE); mexr.enable(); // 注册线程启动事件 ThreadStartRequest tsr = mgr.createThreadStartRequest(); tsr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); tsr.enable(); // 注册线程结束事件 ThreadDeathRequest tdr = mgr.createThreadDeathRequest(); tdr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); tdr.enable(); |
分析器从事件队列中获取事件
EventQueue 用来管理目标虚拟机实例的事件,事件会被加入 EventQueue 中。分析器调用 EventQueue.remove(),如果事件队列中存在事件,则返回不可修改的 EventSet 实例,否则分析器会被挂起直到有新的事件发生。处理完 EventSet 中的事件后,调用其 resume() 方法唤醒 EventSet 中所有事件发生时可能挂起的线程。
清单 5. 获取事件
public void run() { EventQueue queue = vm.eventQueue(); while (connected) { try { EventSet eventSet = queue.remove(); EventIterator it = eventSet.eventIterator(); while (it.hasNext()) { handleEvent(it.nextEvent()); } eventSet.resume(); } catch (InterruptedException exc) {// Ignore } catch (VMDisconnectedException discExc) { handleDisconnectedException(); break; } } } |
获取多线程执行信息
执行流程和变量信息是调试程序最重要的两方面。无论是通过 IDE 设置断点的调试方式,还是通过在程序中记 Log 的调试方式,它们的主要目的是向开发者提供以上两方面信息。本文分析器以单个线程为单位,来记录线程运行信息:
帧 (StackFrame) 详情
关于帧的详细介绍,可以参见 这里。
执行流程。分析器以方法作为最小颗粒度单位。分析器按照实际的线程执行顺序记录方法进出。
变量值。对于单个方法而言,其程序逻辑固定,方法的输入值决定了方法内部执行流程。分析器将在方法入口和出口分别记录该方法作用域内可见变量,便于开发者调试。
执行栈信息记录。当异常发生时,执行栈中完好地保存了调用帧信息。分析器获取线程栈中的所有帧,并记录每个帧记录的信息,其中包含可见变量值、帧调用名称等信息。StackFrame 中变量信息的获取也是 JDI 所提供的特殊能力之一。
与 IDE 设置断点的方法相比,提供的数据信息量相当,但分析器提供执行流程信息更加的清晰;与在程序中记录 Log 的方式相比,分析器在执行流程和信息量两方面都胜出。
以下将详细介绍上面三方面信息抓取:
线程执行流程
线程执行流程可划分:线程启动→ run() →进入方法→ ... →退出方法→线程结束。通过向虚拟机实例注册 ThreadStartRequest,MethodEntryRequest,MethodExitRequest 和 ThreadDeathRequest 事件的方式记录执行过程。事件注册详细见清单 4,清单 6 列出分析器对于以上事件的处理方法。
清单 6. 获取执行流程
void threadStartEvent(ThreadStartEvent event) { println("Thread " + event.thread().name() + " Start"); } void methodEntryEvent(MethodEntryEvent event) { println("Enter Method:" + event.method().name() + " -- " + event.method().declaringType().name()); // 进入方法记录可见变量值 this.printVisiableVariables(); } void methodExitEvent(MethodExitEvent event) { println("Exit Method:" + event.method().name() + " -- " + event.method().declaringType().name()); // 退出方法记录可见变量值 this.printVisiableVariables(); } void threadDeathEvent(ThreadDeathEvent event) { println("Thread " + event.thread().name() + " Dead"); } |