Java虚拟机诊断利器

背景
最近学习Java字节码过程中遇到了反射,有段代码是这样的:
packagecom.example.classstudy;importjava.lang.reflect.Method;/**
* @author TY
*/publicclassReflectionTest {privatestaticintcount=0;publicstaticvoidfoo() {newException("test#"+ (count++)).printStackTrace(); }publicstaticvoidmain(String[] args)throwsException {Class clz =Class.forName("com.example.classstudy.ReflectionTest"); Method method = clz.getMethod("foo");for(inti =0; i <20; i++) { method.invoke(null); } }}
就是一段简单的反射调用 foo 方法,执行 20 次,然后看执行结果:

可以看到在 15 次调用 foo 方法后,第 16 次调用 foo 方法是走的 GeneratedMethodAccessor1 来调用的。我嘞个擦,怎么回事,调着调着就不一样了,于是跟代码,跟到了下面这个类:

其中这句代码就是对反射调用的次数做了控制
if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { MethodAccessorImpl var3 = (MethodAccessorImpl) (new MethodAccessorGenerator()) .generateMethod(this.method.getDeclaringClass(),this.method.getName(),this.method.getParameterTypes(),this.method.getReturnType(),this.method.getExceptionTypes(),this.method.getModifiers());this.parent.setDelegate(var3); }
this.numInvocations 的默认值是 0,而 ReflectionFactory.inflationThreshold() 默认是 15,当大于 15 的时候会通过 ASM 技术动态生成 GeneratedMethodAccessor1 类来调用 invoke 方法, 但是,因为是动态生成的,我们怎么才能看到这个类实际长什么样子呢?
Arthas
这个时候,就可以用上阿里的 arthas(阿尔萨斯)了。

首先下载 arthas:
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
然后启动 arthas:
java -jar arthas-boot.jar
启动之后界面长这个样子:

其中什么 23012, 28436 等是当前环境中现有的 java 进程,然后需要连接到哪个进程就输前面的编号(1234 啥的),输了之后回车。那么我首先改写一下最开始的那个程序,让他不退出:
packagecom.example.classstudy;importjava.lang.reflect.Method;/**
* @author TY
*/publicclassReflectionTest {privatestaticintcount=0;publicstaticvoidfoo() {newException("test#"+ (count++)).printStackTrace(); }publicstaticvoidmain(String[] args)throwsException {Class clz =Class.forName("com.example.classstudy.ReflectionTest"); Method method = clz.getMethod("foo");for(inti =0; i <20; i++) { method.invoke(null); } System.in.read(); }}
重新启动程序之后,查看 arthas 界面:

可以看到 32480 正是我们运行的程序,输入编号 2 去连接到该进程:

然后就可以将动态生成的类 dump 下来:
dump sun.reflect.GeneratedMethodAccessor1

可以看到字节码被 dump 下来了,找到该文件用 javap 来查看:
javap -c -v -p -l GeneratedMethodAccessor1.class

没有问题,可以查看到,然后剩下的就是人肉翻译字节码啦。。。
本篇关于Arthas的使用其实很少,我只是因为学到这个地方简单的用了下,但是已经感受到了 Arthas 的强大之处,它甚至还支持 web 界面。。。

相当厉害!