首页 > 编程知识 正文

java反射实现原理(什么是java反射)

时间:2023-05-03 21:55:45 阅读:68110 作者:2754

Java反射的应用非常广泛,例如spring的核心功能控制反向IOC是通过反射来实现的。 本文主要研究发送方法调用的实现方法和反射对性能的影响。

Method类的invoke方法如下所示。 可以看到,此方法实际上是将反射方法的调用委托给MethodAccessor,并在MethodAccessor的invoke方法中完成了方法调用。 该接口有两个现有的实现类:直接调用本地方法和使用委派模式。 Mehod的实例在第一次调用时生成代理实现,它实现了本地实现。 这相当于在Method和本地实现之间添加中间层。 本地实现很容易理解参数已准备好并进入目标方法。 用以下代码进行简单的测试。 从打印的堆栈信息中可以看到,Method实例的invoke方法首先调用委托DelegatingMethodAccessorImpl实现的invoke方法,最后调用本地实现。 那么,为什么要添加看起来额外的中间委托层呢? 事实上,用java语言实现反射的方法除了本地实现外,还通过动态生成字节码的方法直接调用目标方法。 使用代理模式的目的是在本地实现和动态实现之间切换。

公共类测试反射{

公共统计语音(inti ) {

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

}

publicstaticvoidmain (字符串[ ] args ) throws Exception { )。

class klass=class.forname (com.Kafka.demo.com.Kafka.demo.test.test reflect ' );

方法方法=klass.get method (' target ',int.class );

method.invoke(null,128 );

}

}

方法调用堆栈如下

让我们看看Java.lang.runtime exception :方法调用堆栈

atcom.Kafka.demo.com.Kafka.demo.test.test reflect.target (test reflect.Java :12 )。

at sun.reflect.nativemethodaccessorimpl.invoke0(native method ) )

at sun.reflect.nativemethodaccessorimpl.invoke (nativemethodaccessorimpl.Java 336062 )。

at sun.reflect.delegatingmethodaccessorimpl.invoke (delegatingmethodaccessorimpl.Java :43 )。

at Java.lang.reflect.method.invoke (method.Java :498 )。

atcom.Kafka.demo.com.Kafka.demo.test.test reflect.main (test reflect.Java 336047 )。

与本地实现相比,动态实现的效率约为本地实现的20倍,但第一次生成字节码需要很长时间,因此,仅调用一次,本地实现的性能反而是动态实现的3到4倍。 JVM在参数- dsun.reflect.inflation threshold中进行调整,默认为15次。 也就是说,前15次调用使用本地实现,15次后生成字节码,并采用动态实现方式。 方法调用循环20次的结果如下所示。

在Java.lang.runtime exception :中,我们来看看第十五次方法调用堆栈

atcom.Kafka.demo.com.Kafka.demo.test.test reflect.target (test reflect.Java :12 )。

at sun.reflect.nativemethodaccessorimpl.invoke0(native method ) )

at sun.reflect.nativemethodaccessorimpl.invoke (nativemethodaccessorimpl.Java 336062 )。

at sun.reflect.delegatingmethodaccessorimpl.invoke (delegatingmethodaccessorimpl.Java :43 )。

at Java.lang.reflect.method.invoke (method.Java :498 )。

atcom.Kafka.demo.com.Kafka.demo.test.test reflect.main (test reflect.Java 336048 )。

在Java.lang.runtime exception :中,我们来看看第十六次方法调用堆栈

at com.kafka.demo.com.kafka.dem

o.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了。

一般都认为反射是比较消耗性能的,就连甲骨文关于反射的教学中也强调反射性能开销大的缺点。从上面的例子来看,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);

}

}

}

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。