借助HotSpot SA浅析动态代理与反射机制的实现

13 篇文章 1 订阅
7 篇文章 0 订阅

今天我们借助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接口。这个方法实际上主要做了两件事,

  1. 动态生成代理类;
  2. 反射出代理类实例;

上面已经说过,通过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 ^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值