今天我们借助HotSpot SA中的一个工具,ClassDump,来看下Java的动态代理以及反射机制是如何实现的。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);
}
}
运行一下,
# java -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,
# java 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 la ^_^