反射概念:
- 是指程序在运行状态中,对于任意一个类,都可以知道这个类的所有属性和方法;对于任意一个对象,都能够调用他的任意方法和属性。
**代码示例**
Class myclass = Class.forName("chb.test.reflect.Student");
Method m = myclass.getDeclaredMethod("mymethod",String.class);
m.invoke(myclass.newInstance(),"suncat");
带来的问题:
1)性能问题:使用反射会比直接代码执行慢,因为需要额外的步骤去解析类结构、查找方法或字段等。此外,JVM也无法对涉及反射的代码进行某些优化(如内联),这可能导致性能下降。
2)安全限制:反射可以绕过封装(Encapsulation),例如访问私有字段和方法,这可能会破坏类的封装性,导致潜在的安全风险。为了防止这种情况,安全管理器(Security Manager)可以被配置为限制反射的使用。
3)编译时类型检查缺失:由于反射是在运行时才确定要调用的方法或访问的字段,因此无法在编译期进行类型检查。这增加了运行时出现ClassCastException或其他异常的风险。
反射原理:
其实反射主要有两个主要的部分:Method的获取、Method的使用
一、Method的获取

getDeclaredMethod()方法中也主要就是通过searchMethods()来查找该class中声明的方法列表,如果找到相对应的Method,则复制一份出来。所次每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象(如果需要频繁调用,最好把Method对象缓存起来)。
- 获取该class中声明的方法列表
getDeclaredMethod的流程也就这样,那其中的该class中声明的方法列表是怎么实现的呢?
它会通过privateGetDeclaredMethods()方法来获取,其内部实现是先利用reflectionData()方法去查找缓存中是否有该列表的缓存
(用数据结构ReflectionData来缓存从JVM中读取类的属性数据。ReflectionData对象是SoftReference类型的,说明在内存紧张时可能会被回收。如果ReflectionData被回收之后,又执行了反射方法,那只能通过newReflectionData()方法重新创建一个这样的对象了)
(1)如果缓存中找到相应的列表,则直接返回结果。
(2)如果没找到,则从JVM中查找。
(2-1)从JVM中查找会利用GetDeclaredMethod()方法。
(2-2)如果找到了,先缓存,后返回数据。
(2-3)如果没找到,则直接返回空。
二、Method的调用

在Method中invoke()方法中,有两个主要步骤:一是获取MethodAccessor对象;二是调用MethodAccessor对象的invoke()方法。
- 1 . 获取MethodAccessor对象
一开始methodAccessor为空,需要调用acquireMethodAccessor生成一个新的MethodAccessor对象。
在acquireMethodAccessor方法中,会通过ReflectionFactory类的newMethodAccessor()方法创建一个实现了MethodAccessor接口的对象。
------------------------------------
newMethodAccessor都会返回DelegatingMethodAccessorImpl对象,DelegatingMethodAccessorImpl对象就是一个代理对象,负责调用被代理对象delegate的invoke方法,其中delegate参数默认是NativeMethodAccessorImpl对象。
------------------------------------
调用次数的不同会改变DelegatingMethodAccessorImpl所代理的对象:
1)调用次数小于等于15次:如果该方法的累计调0.用次数<=15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用native方法实现反射;
2)调用次数大于15:如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl。(改变DelegatingMethodAccessorImpl类中的代理对象)
- 2 . 调用MethodAccessor对象的invoke()方法
由于DelegatingMethodAccessorImpl对象内部实现是一个代理对象,被代理的对象是NativeMethodAccessorImpl(或者MethodAccessorImpl)对象。所以最终Method的invoke方法调用的是NativeMethodAccessorImpl(或者MethodAccessorImpl)对象invoke方法。
------------------------------------
1)调用次数小于等于15,那么MethodAccessor会是NativeMethodAccessorImpl,即调用NativeMethodAccessorImpl#invoke()方法
2)调用次数大于15,那么MethodAccessor会是字节码组装而成的MethodAccessorImpl,即调用MethodAccessorImpl#invoke()方法
invoke()具体里面就是native语句,没必要深究下去。
备注:
- 关于为什么要分NativeMethodAccessorImpl和MethodAccessorImpl?

一开始使用NativeMethodAccessorImpl,但随着调用次数的增加,每次反射都使用JNI跨越native边界会对优化有阻碍作用。
为了发挥了JIT优化的作用,避免了JNI为了维护OopMap(HotSpot用来实现准确式GC的数据结构)进行封装/解封装的所带来的性能损耗,因此使用拼装出的字节码可以直接以Java调用的形式MethodAccessorImpl实现反射。
因此在已经创建了MethodAccessor的情况下,使用Java版本(MethodAccessorImpl)的实现会比native版本(NativeMethodAccessorImpl)更快。所以当调用次数到达一定次数(15次)后,会切换成Java实现的版本,来优化未来可能的更频繁的反射调用。
反射的性能问题
1)代码的验证防御逻辑过于复杂,本来这块验证时在链接阶段实现的,使用反射reflect时需要在运行时进行;
2)产生过多的临时对象,影响GC的消耗;
3)由于缺少上下文,导致不能进行更多的优化,如JIT;
---------------------------------------------
备注:现代JVM已经运行的足够快,我们应该把主要重心放在复杂的代码逻辑上,而不是一开始就进行各种性能优化。
更多java基础总结(适合于java基础学习、java面试常规题):
总结篇(9)---字符串及基本类 (1)字符串及基本类之基本数据类型
总结篇(10)---字符串及基本类 (2)字符串及基本类之java中公共方法及操作
总结篇(12)---字符串及基本类 (4)Integer对象
总结篇(14)---JVM(java虚拟机) (1)JVM虚拟机概括
总结篇(15)---JVM(java虚拟机) (2)类加载器
总结篇(16)---JVM(java虚拟机) (3)运行时数据区
总结篇(17)---JVM(java虚拟机) (4)垃圾回收
总结篇(18)---JVM(java虚拟机) (5)垃圾回收算法
总结篇(19)---JVM(java虚拟机) (6)JVM调优
总结篇(24)---Java线程及其相关(2)多线程及其问题
总结篇(25)---Java线程及其相关(3)线程池及其问题
总结篇(26)---Java线程及其相关(4)ThreadLocal
总结篇(27)---Java并发及锁(1)Synchronized
总结篇(31)---JUC工具类(1)CountDownLatch
本文介绍了Java反射的概念、原理、Method的获取与调用,以及反射的性能问题。反射可在运行时获取类的属性和方法,Method获取涉及缓存查找,调用根据次数切换实现版本。反射存在验证逻辑复杂、产生临时对象多等性能问题,但现代JVM运行快,不必过早优化。
8644

被折叠的 条评论
为什么被折叠?



