Java虚拟机提供了一套用于调试(JVMDI)和监视(JVMPI)的接口,Java5之后统一为JVMTI: http://docs.oracle.com/javase/1.5.0/docs/guide/jvmti/ 。
其中JVMDI分为三个部分:JVMDI,JDWP和JDI . http://docs.oracle.com/javase/1.4.2/docs/guide/jpda/architecture.html
这篇就是简单的介绍一下怎么使用JDI去监视程序的运行的。
首先假设有一个简单的程序:
package test; public class Test { public static void main(String[] args) { new Thread() { @Override public void run() { Test test = new Test(); while (true) { try { sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } test.printHello(); } } }.start(); } protected void printHello() { System.out.println("hello"); } }
程序中,每隔五秒种,printHello()方法就会执行一次。
如果你希望每次printHell()被执行的时候通知你一下,在不修改代码的情况下,你要怎么办?没办法吧?
看看JDI的定义:
JDI - Java Debug Interface Defines a high-level Java language interface which tool developers can easily use to write remote debugger applications.
所以首先,我们先以远程调试的方式启动上面的Test类:
java -Xdebug -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8800 -cp . test.Test
大致就是以socket传输的方式调试Test类,调试的连接端口为8800,并且连接过程不挂起。
一量启动,就可以看到如下的输出:
Listening for transport dt_socket at address: 8800 hello hello hello
这样Server被调试端就准备好了,下面就是写监听端了。这里就要用到jdk中提供的JDI接口了。要使用此接口,我们需要在类路径里包含JDK下的tools.jar等包,可以在<JDK>/lib目录下找着。
VirtualMachineManager vmm = Bootstrap.virtualMachineManager(); List<AttachingConnector> connectors = vmm.attachingConnectors(); SocketAttachingConnector sac = null; for (AttachingConnector ac : connectors) { if (ac instanceof SocketAttachingConnector) { sac = (SocketAttachingConnector) ac; break; } } if (sac == null) { System.out.println("JDI error"); return; }
Map arguments = sac.defaultArguments(); Connector.Argument hostArg = (Connector.Argument) arguments.get(HOST); Connector.Argument portArg = (Connector.Argument) arguments.get(PORT); hostArg.setValue("127.0.0.1"); portArg.setValue(String.valueOf(8800)); vm = sac.attach(arguments);
List<ReferenceType> classesByName = vm.classesByName("test.Test"); if (classesByName == null || classesByName.size() == 0) { System.out.println("No class found"); return; } ReferenceType rt = classesByName.get(0); List<Method> methodsByName = rt.methodsByName("printHello"); if (methodsByName == null || methodsByName.size() == 0) { System.out.println("No method found"); return; } Method method = methodsByName.get(0);
vm.setDebugTraceMode(VirtualMachine.TRACE_EVENTS); vm.resume(); EventRequestManager erm = vm.eventRequestManager(); MethodEntryRequest methodEntryRequest = erm.createMethodEntryRequest(); methodEntryRequest.addClassFilter(rt); methodEntryRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); methodEntryRequest.enable(); BreakpointRequest breakpointRequest = erm .createBreakpointRequest(method.location()); breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); breakpointRequest.enable(); eventLoop();
这里监听每次方法入口的时候,以及在方法上注册一个断点,发一个通知。
private static void eventLoop() throws Exception { eventQueue = vm.eventQueue(); while (true) { if (vmExit == true) { break; } eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); while (eventIterator.hasNext()) { Event event = (Event) eventIterator.next(); execute(event); } } } private static void execute(Event event) throws Exception { if (event instanceof VMStartEvent) { System.out.println("VM started"); eventSet.resume(); } else if (event instanceof BreakpointEvent) { System.out .println("Reach Method printHello of test.Test"); eventSet.resume(); } else if (event instanceof MethodEntryEvent) { MethodEntryEvent mee = (MethodEntryEvent) event; Method method = mee.method(); System.out.println(method.name() + " was Entered!"); eventSet.resume(); } else if (event instanceof VMDisconnectEvent) { vmExit = true; } else { eventSet.resume(); } }
最后看输出:
[JDI: EventSet: SUSPEND_EVENT_THREAD] [JDI: Event: [email protected]:23 in thread Thread-0] [JDI: Event: [email protected]:23 in thread Thread-0] printHello was Entered! Reach Method printHello of test.Test [JDI: EventSet: SUSPEND_EVENT_THREAD] printHello was Entered! [JDI: Event: [email protected]:23 in thread Thread-0] [JDI: Event: [email protected]:23 in thread Thread-0] Reach Method printHello of test.Test