注册事件

  分析器和目标程序之间采用基于事件的模式进行通信。分析器向虚拟机实例注册所关注的事件。事件发生时,虚拟机将相关事件信息放入事件队列中,采用 生产者 - 消费者 的模式与分析器同步。

  注册事件

  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 的调试方式,它们的主要目的是向开发者提供以上两方面信息。本文分析器以单个线程为单位,来记录线程运行信息:

  如何看Java debug端口是否打开 java debug interface_调试帧 (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"); 
   }