java反射如何实现的_Java反射实现原理

math?formula=%5Ccolor%7BForestGreen%7D%7B%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%8E%B0%7D

Java反射应用十分广泛,例如spring的核心功能控制反转IOC就是通过反射来实现的,本文主要研究一下发射方法调用的实现方式和反射对性能的影响。

如下为Method类中invoke方法,可以看出该方法实际是将反射方法的调用委派给MethodAccessor,通过MethodAccessor的invoke方法完成方法调用。该接口有两个现有的实现类,一个是直接调用本地方法,另一个是通过委派模式。Mehod的实例第一次调用都会生成一个委派实现,它实现的就是一个本地实现,相当于在Method和本地实现之间加了一个中间层,本地实现比较好理解,就是讲参数准备好,然后进入目标方法。通过如下代码进行一个简单的测试,通过打印的堆栈信息可以看出,Method实例的invoke方法首先调用的是委派实现DelegatingMethodAccessorImpl的invoke方法,最后调用本地实现。那么为什么会加上一个看似多余的中间委派层呢?实际上,java语言实现反射的方法除了本地实现之外,还会采用动态生成字节码的方式直接调用目标方法,运用委派模式的目的就是在本地实现和动态实现之间进行切换。

public class TestReflect {

public static void target(int i) {

new RuntimeException("让我们来看看方法调用栈").printStackTrace();

}

public static void main(String[] args) throws Exception {

Class> klass = Class.forName("com.kafka.demo.com.kafka.demo.test.TestReflect");

Method method = klass.getMethod("target", int.class);

method.invoke(null, 128);

}

}

方法调用栈如下所示

java.lang.RuntimeException: 让我们来看看方法调用栈

at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)

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.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:47)

动态实现和本地实现相比,其运行效率是本地实现的20倍左右,但是第一次生成字节码是比较耗时的,所以仅单次调用的话,本地实现的性能反而是动态实现的3到4倍。JVM通过参数-Dsun.reflect.inflationThresHold来进行调整,默认是15次,即前15次调用会使用本地实现,15次之后会生成字节码,采用动态实现的方式。我们将方法的调用循环20次得到结果如下所示:

java.lang.RuntimeException: 让我们来看看第15次方法调用栈

at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)

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.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)

java.lang.RuntimeException: 让我们来看看第16次方法调用栈

at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)

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.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)

java.lang.RuntimeException: 让我们来看看第17次方法调用栈

at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)

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.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)

在第十六次方法调用的过程中动态生成了字节码,并在第十七次方法调用的过程中就直接使用GenerateMethodAccessor1了。

math?formula=%5Ccolor%7BForestGreen%7D%7B%E5%8F%8D%E5%B0%84%E6%80%A7%E8%83%BD%7D

一般都认为反射是比较消耗性能的,就连甲骨文关于反射的教学中也强调反射性能开销大的缺点。从上面的例子来看,Class.forName()需要调用本地方法,getMethod()方法需要遍历目标类的所有方法,如果没有匹配到还需要遍历父类的所有方法,这些都是比较消耗性能的操作,但是我们可以将它们的实例对象放在缓存中,来减小对性能的影响,这里我们主要讨论Method实例的invoke方法对性能的影响。我做了一个简单的实验来进行验证,将一个空方法直接调用20亿次,每1亿次打印一个该阶段的运行时间,取最后五次时间求取平均值(这样做的目的是为了得到预热峰值性能),在我32G内存的ThinkStation上面直接调用1亿次耗时90ms,通过反射调用平均耗时为300ms,约为直接调用的3.3倍。前文中介绍过动态实现和本地实现,由于本地实现比较消耗性能,通过-Dsun.reflect.noInflation=true来关闭本地实现直接使用动态实现,此时平均耗时为270ms,约为直接调用的3倍。此外方法调用时需要检查方法的权限,如果跳过权限检测过程,即设置method.setAccessible(true),此时平均耗时为210ms,约为直接调用的2倍左右,至此我们可以看出对于反射的应用会对性能造成一定的影响,但是可以通过优化减小影响。那是不是为了性能就杜绝使用反射呢?显然不是,前文中提到IOC就是用反射实现的,类似接口和实现类,接口的使用也是对性能有影响的(虽然这个影响可以忽略不计,也许这个类比也不太确切),但是不能为了一点性能的提升而放弃优雅的设计模式。

public class TestReflect {

public static void target(int i) {

// 空方法

}

public static void main(String[] args) throws Exception {

//-Dsun.reflect.noInflation=true

Class> klass = Class.forName("com.kafka.demo.com.kafka.demo.test..TestReflect");

Method method = klass.getMethod("target", int.class);

//method.setAccessible(true);

LocalDateTime t1 = LocalDateTime.now();

for (int i = 1; i <= 2_000_000_000; i++) {

if (i % 100_000_000 == 0) {

long temp = System.currentTimeMillis();

System.out.println(Duration.between(t1, LocalDateTime.now()).toMillis());

t1 = LocalDateTime.now();

}

//method.invoke(null, 128);

target(128);

}

}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值