java反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;

获取field过程:

1:校验成员变量是否允许被访问:
        允许被访问,跳转到步骤2
        否则,抛出SecurityException异常;
2:调用searchFields方法获取属性名为name的field;
3:如果步骤2的field为null,抛出NoSuchFieldException异常,首先先找缓存,如果缓存没有就调用jvm本地方法生成一份。在找到指定的Field之后,会copy一份返回。保证每次返回的对象是不同的。
用一个ReflectionData的soft引用当缓存。ReflectionData主要是缓存了Field、Method和Constructor等相关数据。最开始缓存为空,就调用jvm本地方法获取,然后将查到的信息缓存到ReflectionData里面。维护着ReflectionData的soft引用,所以fullgc(有jvm参数可以让soft引用提前回收)的时候会被回收。下次用的时候再次生成。如果缓存没有被清理,就从缓存取出这一份,然后copy。缓存被清了,就重新生成一份缓存。

获取Method与field过程基本一致。

Method的调用的过程:

1:校验该方法是否允许被访问,允许被访问则跳转到步骤2,否则,抛出IllegalAccessException异常;
2:获取当前method的MethodAccessor对象ma,如ma为null,则调用acquireMethodAccessor方法获取;
3:调用MethodAccessor对象的invoke方法来实现调用,整个invoke方法的核心也在这里。

从同一份缓存copy出来的每个Method对象的root属性都指向同一个Method root。不同缓存不同。
Method的MethodAccessor赋值:首先看root对象是否拥有,没有就新创一个赋值给root,然后用这个MethodAccessor。即所有同一份缓存copy出来的Method对象共用一个MethodAccessor。

Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。Method.invoke委派给了MethodAccessor处理,MethodAccessor是 DelegatingMethodAccessorImpl。DelegatingMethodAccessorImpl 再一次委派给了NativeMethodAccessorImpl。默认情况下,会调用本地方法 invoke0 ,直接在 JVM 中执行方法调用逻辑,但这里会切换到本地方法调用,存在开销;当 method 实例 A 调用次数超过阈值(默认15,可以指定 VM 参数-Dsun.reflect.inflationThreshold=15 调整)的时候,会 new 一个新的 MethodAccessorImpl GeneratedMethodAccessorXX。这个GeneratedMethodAccessorXX 会替换掉 DelegatingMethodAccessorImpl中的委托实例,完成切换。这个切换的过程俗称 inflation。
注意:这里的 XX 表示一个自增的数字,表示允许系统中可以有多个 GeneratedMethodAccessor 同时存在。

JVM的优化:当某个反射调用形成热点(达到足够的调用次数,15次)后,这些类的字节码就会被临时生成,这就是 inflation。inflation 就是通过生成字节码的方式,将某个方法的反射调用偷换成了某个具体实例对其自身方法的调用,本质就是“反射”切换成“非反射” 的过程。大大减少了本地方法调用的频次,进而提升了调用性能。

反射开销:

1:Class.forName 会尝试先去方法区(1.8以后就是 Metaspace) 中寻找有没有对应的类,如果没有则在 classpath 中找到对应的class文件, 通过类加载器将其加载到内存里。注意这个方法涉及本地方法调用,这里本地方法调用的切换、类的搜索以及类的加载都存在开销;
2:newInstance ,开辟内存空间,实例化已经找到的 Class;
3:getDeclaredMethod/getMethod遍历 Class 的方法,以方法签名匹配出所需要的 Method 实例,也是比较重的开销所在;虽然 java.lang.Class 内部维护了一套名为 reflectionData 的数据结构,其本身是 SoftReference 的。Class 会将匹配成功的 method 缓存到这里,以供下次访问命中,这样一来会减轻 method 遍历的开销,但是会增加额外的堆空间消耗(以空间置换时间);
4:反射执行调用本地方法效率相对较低,优化后,等于重新加载了一个类,有着一定的空间开销。
5:当reflectionData缓存被清了,同一个method名的方法反射需要重新优化,即调用15次后jvm重新定义一个类,重新加载。这样会占用元空间的容量。如果缓存清理频繁(如果设置SoftRefLRUPolicyMSPerMB=0,yanggc就会回收soft对象),就会大量占用元空间。

参考
https://www.zhihu.com/questio...
https://www.jianshu.com/p/2bc...

你可能感兴趣的:(后端)