HotSpot SA(2):ClassDump
今天继续介绍HotSpot SA中的另一个好玩的工具,ClassDump。ClassDump可以在运行时dump类文件,我们可以用来dump一些动态生成或者运行时被修改了字节码的类。下面就借助ClassDump来看一看动态代理跟反射机制在运行时偷偷干的一些事情。
动态代理
看下面的栗子先,
public interface Foo { void bar(); }
public class FooImpl implements Foo { @Override public void bar() { System.out.println("hello FooImpl."); } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class FooHandler implements InvocationHandler { private Foo foo; public FooHandler(Foo foo) { this.foo = foo; } @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("before foo..."); method.invoke(foo); System.out.println("after foo..."); return null; } }
import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) throws Exception{ Foo foo = new FooImpl(); FooHandler fooHandler = new FooHandler(foo); Foo fooProxy = (Foo) Proxy.newProxyInstance( foo.getClass().getClassLoader(), foo.getClass().getInterfaces(), fooHandler); fooProxy.bar(); System.in.read(); } }
通过Proxy#newProxyInstance获得了一个代理类的实例,这个代理类实现了Foo接口。这个方法实际上主要做了两件事,
- 动态生成代理类;
- 反射出代理类实例;
上面已经说过,通过ClassDump我们可以将第1步运行时动态生成的代理类dump下来。dump时需要使用ClassFilter来告诉SA我们需要dump哪些类。下面我们用类的名字来过滤,那么这个动态代理类的类名又是啥?看JDK源码,代理类的生成逻辑在ProxyClassFactory中,类名生成逻辑如下,
// prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy";
if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;
所以ClassFilter可以这样写,
import sun.jvm.hotspot.oops.InstanceKlass; import sun.jvm.hotspot.tools.jcore.ClassFilter; public class ClassNameFilter implements ClassFilter { private static final String CLASSNAME_PREFIX = "com/sun/proxy/$Proxy"; @Override public boolean canInclude(InstanceKlass instanceKlass) { String klassName = instanceKlass.getName().asString(); return klassName.startsWith(CLASSNAME_PREFIX); } }
运行一下,
# java7 -Dsun.jvm.hotspot.tools.jcore.filter=me.kisimple.just4fun.ClassNameFilter sun.jvm.hotspot.tools.jcore.ClassDump 6962 Attaching to process ID 6962, please wait... Debugger attached successfully. Server compiler detected. JVM version is 24.65-b04 # tree com/ com/ └── sun └── proxy └── $Proxy0.class
用jd反编译一下就能看到这个动态生成的代理类的"源码"了,
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import me.kisimple.just4fun.Foo; public final class $Proxy0 extends Proxy implements Foo { private static Method m3; private static Method m1; private static Method m0; private static Method m2; public final void bar() { try { // this.h就是通过构造函数传进来的FooHandler this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } static { try { m3 = Class.forName("me.kisimple.just4fun.Foo").getMethod("bar", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } public final boolean equals(Object paramObject) {...} public final String toString() {...} public final int hashCode() {...}
所以当我们调用代理类的方法,也就是fooProxy.bar时实际上调用的就是fooHandler.invoke方法了,所以输出是这样的,
before foo... hello FooImpl. after foo...
反射机制
接下来看下反射的栗子,
public class Main { public static void main(String[] args) throws Exception{ Class klass = Class.forName("me.kisimple.just4fun.FooImpl"); for(int i = 1; i <= 16; i++) { System.out.println("i = " + i); klass.newInstance(); Thread.sleep(1000); } System.in.read(); } }
加上-verbose选项来运行,从加载到我们的Main开始看下,
[Loaded me.kisimple.just4fun.Main from file:/home/blues/Projects/just4fun/out/production/just4fun/] [Loaded java.net.AbstractPlainSocketImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.PlainSocketImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.SocksSocketImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.security.action.LoadLibraryAction from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.lang.IllegalAccessException from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.lang.reflect.TypeVariable from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.lang.annotation.Annotation from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.NativeMethodAccessorImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.DelegatingMethodAccessorImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded me.kisimple.just4fun.Foo from file:/home/blues/Projects/just4fun/out/production/just4fun/] [Loaded me.kisimple.just4fun.FooImpl from file:/home/blues/Projects/just4fun/out/production/just4fun/] [Loaded java.nio.CharBuffer from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.nio.HeapCharBuffer from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.nio.charset.CoderResult from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.nio.charset.CoderResult$Cache from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.nio.charset.CoderResult$1 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.nio.charset.CoderResult$2 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] i = 1 [Loaded java.net.SocketAddress from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetSocketAddress from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddress from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetSocketAddress$InetSocketAddressHolder from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.security.action.GetBooleanAction from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddress$InetAddressHolder from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddress$Cache from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddress$Cache$Type from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddressImplFactory from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddressImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Inet6AddressImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.net.spi.nameservice.NameService from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.InetAddress$1 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Inet4AddressImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Inet4Address from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.SocketException from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.net.NetHooks from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.net.NetHooks$Provider from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.net.sdp.SdpProvider from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Inet6Address from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Inet6Address$Inet6AddressHolder from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.net.Socket from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10 i = 11 i = 12 i = 13 i = 14 i = 15 i = 16 [Loaded sun.reflect.ClassFileConstants from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.AccessorGenerator from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.MethodAccessorGenerator from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ByteVectorFactory from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ByteVector from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ByteVectorImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ClassFileAssembler from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.UTF8 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.Label from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.Label$PatchInfo from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded java.util.ArrayList$Itr from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.MethodAccessorGenerator$1 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ClassDefiner from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.ClassDefiner$1 from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar] [Loaded sun.reflect.GeneratedConstructorAccessor1 from __JVM_DefineClass__] [Loaded sun.reflect.BootstrapConstructorAccessorImpl from /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar]
可以看到当我们第16次调用Class#newInstance时加载了一些类进来。这其实是因为Java的反射机制使用了Inflation Mechanism,在sun.reflect.ReflectionFactory有如下说明,
// "Inflation" mechanism. Loading bytecodes to implement // Method.invoke() and Constructor.newInstance() currently costs // 3-4x more than an invocation via native code for the first // invocation (though subsequent invocations have been benchmarked // to be over 20x faster). Unfortunately this cost increases // startup time for certain applications that use reflection // intensively (but only once per class) to bootstrap themselves. // To avoid this penalty we reuse the existing JVM entry points // for the first few invocations of Methods and Constructors and // then switch to the bytecode-based implementations. // // Package-private to be accessible to NativeMethodAccessorImpl // and NativeConstructorAccessorImpl private static boolean noInflation = false; private static int inflationThreshold = 15;
Class#newInstance背后调用的是Constructor#newInstance方法。也就是说,出于性能考虑,当我们对一个类newInstance次数在ReflectionFactory#inflationThreshold=15以下时,将会使用本地方法来反射,而超过这个阈值就会动态生成Constructor来反射,这个逻辑是在NativeConstructorAccessorImpl#newInstance中,
public Object newInstance(Object[] args) throws InstantiationException, IllegalArgumentException, InvocationTargetException { if (++numInvocations > ReflectionFactory.inflationThreshold()) { ConstructorAccessorImpl acc = (ConstructorAccessorImpl) new MethodAccessorGenerator(). generateConstructor(c.getDeclaringClass(), c.getParameterTypes(), c.getExceptionTypes(), c.getModifiers()); parent.setDelegate(acc); } return newInstance0(c, args); }
MethodAccessorGenerator#generateConstructor会通过写字节码的方式生成一个ConstructorAccessorImpl。
回到ClassDump,还是刚才那个问题,看下类名的生成逻辑,MethodAccessorGenerator#generateName,
private static synchronized String generateName(boolean isConstructor, boolean forSerialization) { if (isConstructor) { if (forSerialization) { int num = ++serializationConstructorSymnum; return "sun/reflect/GeneratedSerializationConstructorAccessor" + num; } else { int num = ++constructorSymnum; return "sun/reflect/GeneratedConstructorAccessor" + num; } } else { int num = ++methodSymnum; return "sun/reflect/GeneratedMethodAccessor" + num; } }
把刚才写的ClassFilter稍微改下,
private static final String CLASSNAME_PREFIX = "sun/reflect/GeneratedConstructorAccessor";
dump之后,本来还是想用jd反编译,但是不知道为啥jd反编译不出来,换另一个工具,soot,
# java7 soot.Main -cp .:/usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar:/usr/lib/jvm/java-7-openjdk-i386/jre/lib/jce.jar -f dava sun.reflect.GeneratedConstructorAccessor1 Soot started on Sun Feb 01 17:28:30 CST 2015 Decompiling sun.reflect.GeneratedConstructorAccessor1... Analyzing sootOutput/dava/src/sun/reflect/GeneratedConstructorAccessor1.java... Generating sootOutput/dava/src/sun/reflect/GeneratedConstructorAccessor1.java... Soot finished on Sun Feb 01 17:28:35 CST 2015 Soot has run for 0 min. 5 sec. # tree sootOutput/ sootOutput/ └── dava ├── classes └── src └── sun └── reflect ├── build.xml └── GeneratedConstructorAccessor1.java
package sun.reflect; import me.kisimple.just4fun.FooImpl; import java.lang.reflect.InvocationTargetException; public class GeneratedConstructorAccessor1 extends ConstructorAccessorImpl { public Object newInstance(Object[] r1) throws java.lang.reflect.InvocationTargetException { FooImpl $r2; label_0: { try { if (r1 == null || r1.length == 0) { break label_0; } throw new IllegalArgumentException(); } catch (NullPointerException $r6) { } catch (NullPointerException $r6) { } throw new IllegalArgumentException($r6.toString()); } //end label_0: try { $r2 = new FooImpl(); } catch (Throwable $r4) { throw new InvocationTargetException($r4); } return $r2; } }
所以当我们Class#newInstance或者Constructor#newInstance其实已经是直接new FooImpl()返回了。
alright,今天就先到这。Have fun ^_^