反射实现原理

本文深入探讨了Java反射的实现原理,包括反射的膨胀(inflation)机制。当反射调用超过15次时,JVM会使用ASM动态生成类以提高性能。这种机制平衡了首次生成成本与多次调用的效率。同时,文章通过示例展示了如何观察和理解这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

反射的实现原理

Java的反射机制允许我们运行时动态地调用某个对象的方法、新建对象实例、获取对象的属性等。

Connected to the target VM, address: '127.0.0.1:49230', transport: 'socket'
java.lang.Exception: test#0
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#1
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#2
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#3
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#4
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#5
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#6
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#7
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#8
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#9
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#10
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#11
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#12
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#13
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#14
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#15
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#16
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#17
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#18
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
java.lang.Exception: test#19
	at com.zc.srjvm.c06.ReflectionTest.foo(ReflectionTest.java:9)
	at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.zc.srjvm.c06.ReflectionTest.main(ReflectionTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:49230', transport: 'socket'

Process finished with exit code 0

发现第16次之后,就没有了invoke0()方法的调用.

同一段代码,运行的堆栈结果与执行次数有关,在第0~15次时调用方式为sun.reflect.NativeMethodAccessorImpl.invoke0,从第16次开始调用方式变为了sun.reflect.GeneratedMethodAccessor1.invoke。

反射方法源码分析

Method.invoke方法调用了MethodAccessor.invoke方法,MethodAccessor是一个接口,它的源码如下所示。

public interface MethodAccessor {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}

从输出的堆栈可以看到MethodAccessor的实现类是委托类DelegatingMethodAccessorI-mpl,它的invoke方法非常简单,就是把调用委托给了真正的MethodAccessorImpl实现类。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}

通过堆栈可以看到在第0~15次调用中,实现类是NativeMethodAccessorImpl,从第16次调用开始,实现类是GeneratedMethodAccessor1,玄机就在NativeMethodAccessor-Impl的invoke方法中,如图3-18所示。

前0~15次都会调用invoke0,这是一个native的函数,代码如下所示。

private static native Object invoke0(Method m, Object obj, Object[] args);

15次调用以后会使用新的逻辑,利用GeneratedMethodAccessor1来调用反射的方法。

MethodAccessorGenerator的作用是通过ASM生成新的类sun.reflect.GeneratedMethod-Accessor1。

为了查看生成的类的内容,可以使用阿里的arthas工具。修改上面的代码,在main函数的最后加上“System.in.read();”让JVM进程不要退出。执行arthas工具中的./as.sh,会要求输入JVM进程

选择在运行的进程号为7的ReflectionTest就进入了arthas交互性界面。执行dump sun.reflect.GeneratedMethodAccessor1,将类文件保存到本地,如图3-20所示。

[arthas@54498]$ dump sun.reflect.GeneratedMethodAccessor1
 HASHCODE  CLASSLOADER                                      LOCATION
 7e6cbb7a  +-sun.reflect.DelegatingClassLoader@7e6cbb7a     /Users/duandian/log
             +-sun.misc.Launcher$AppClassLoader@18b4aac2    s/arthas/classdump/
               +-sun.misc.Launcher$ExtClassLoader@788f3202  sun.reflect.Delegat
                                                            ingClassLoader-7e6c
                                                            bb7a/sun/reflect/Ge
                                                            neratedMethodAccess
                                                            or1.class
/Users/duandian/logs/arthas/classdump/sun.reflect.DelegatingClassLoader-7e6cbb7a/sun/reflect/GeneratedMethodAccessor1.class  
package sun.reflect;

import com.zc.srjvm.c06.ReflectionTest;
import java.lang.reflect.InvocationTargetException;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
  public Object invoke(Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
    try {
      if (paramArrayOfObject != null && paramArrayOfObject.length != 0)
        throw new IllegalArgumentException(); 
      try {
        ReflectionTest.foo();
        return null;
      } catch (Throwable throwable) {
        throw new InvocationTargetException(null);
      } 
    } catch (ClassCastException|NullPointerException classCastException) {
      throw new IllegalArgumentException(null.toString());
    } 
  }
}

为什么要设置为0~15次使用native方式来调用,15次以后使用ASM新生成的类来处理反射的调用呢?

这是基于性能的考虑,JNI native调用的方式要比动态生成类调用的方式慢20倍左右,但是由于第一次字节码生成的过程比较慢,如果反射仅调用一次的话,采用生成字节码的方式反而比native调用的方式慢3~4倍。为了权衡这两种方式的利弊,Java引入了inflation机制,接下来我们来看看inflation的细节。

反射的inflation机制

很多情况下,反射只会调用一两次,JVM于是想了一个办法,设置了一个sun.reflect.inflationThreshold阈值,默认等于15。当反射方法调用超过15次时(从0开始计算),会使用ASM生成新类,保证后面的调用比native要快。调用次数小于15次的情况下,直接用native的方式来调用,没有额外类的生成、校验、加载的开销。这种方式被称为inflation机制。

JVM与inflation相关的属性有两个,一个是刚提到的阈值sun.reflect.inflationThreshold,还有一个是是否禁用inflation的属性sun.reflect.noInflation,默认值为false。如果把sun.reflect.noInflation这个值设置成true,那么从第0次开始就使用动态生成类的方式来调用反射方法了,而不会使用native的方式。增加noInflation选项重新执行上述Java代码,如下所示。

java -cp . -Dsun.reflect.noInflation=true ReflectionTest

可以看到,从第0次开始就已经没有使用native方法来调用反射方法了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FlyingZCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值