dynamic-attach-jvm

之前的javaagent文章中介绍了javaagent的一些基本原理和使用java -javaagent的方式,今天介绍一下如何通过javaagent查看、修改运行中的java进程。
工作中常用到的诊断工具如jdk中带有的jstack、jmap或btrace、greys等工具都是利用了这一机制。

jdk.attach中定义了VirtualMachine类,通过这个类的attach(pid)方法,便可以attach到一个运行中的java进程上,之后便可以通过loadAgent(agentJarPath)来将agent的jar包注入到对应的进程了,然后对应的进程会调用agentmain方法。
dynamic-attach

下面用一个打印对应线程各线程状态数量统计的例子来展示实现过程。

首先创建一个多模块的maven工程,一个子工程是agent,里面按照javaagent文章介绍的方式编写即可。
其中agentmain方法里,我们先打印一个Hello World,然后通过JMX获取当前进程所有线程的信息,然后统计各个状态的数量在输出到标准输出中。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HelloWorldAgent {
public static void premain(String args, Instrumentation instrumentation) {
agentmain(args, instrumentation);
}
public static void agentmain(String args, Instrumentation instrumentation) {
System.out.println("Hello World " + new Date());
ThreadInfo[] threadInfos = ManagementFactory.getThreadMXBean().dumpAllThreads(false, false);
Map<Thread.State, Long> stateStatMap = Arrays.stream(threadInfos).collect(Collectors
.groupingBy(t -> t.getThreadState(), Collectors.counting()));
System.out.println(stateStatMap);
}
}

完成agent之后打包,例如我的agent包的位置是/Users/zhengyangliu/Code/opensource/javadynamicattach/attachagent/target/attach-agent-1.0-SNAPSHOT-jar-with-dependencies.jar

然后我们开始编写attach代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) throws Exception {
String pid = args[0];
String jarFileName = args[1];
new Main().start(pid, jarFileName);
}
public void start(String processId, String jarFileName) throws Exception {
VirtualMachine virtualMachine = null;
try {
virtualMachine = VirtualMachine.attach(processId);
virtualMachine.loadAgent(jarFileName);
} finally {
if (virtualMachine != null) {
virtualMachine.detach();
}
}
}

然后我们就可以编译运行我们的attach代码了。要注意的是VirtualMachine类在JDK1.8中还处于com.sun包名下,位于tools.jar包中,所以运行时要指定对应的classpath。
为方便起见,我找了一个运行中的foreground的zookeeper进程id和agent jar包位置作为参数。
java -cp './*:/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/lib/tools.jar:' com.github.liuzhengyang.attach.Main 9807 /Users/zhengyangliu/Code/opensource/javadynamicattach/attachagent/target/attach-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
然后可以在控制台看到成功的打印出了线程统计的信息。
attachedzk

完整的code在github

之后会介绍更多例如修改字节码实现动态替换类方法的功能的相关使用和实现

参考

感觉有收获的话,请我吃个早饭吧!