文档章节

深入分析 Java 方法反射的实现原理

stars永恒
 stars永恒
发布于 2017/04/04 16:00
字数 1071
阅读 14
收藏 0

方法反射实例


    public class ReflectCase {


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

            Proxy target = new Proxy();

            Method method = Proxy.class.getDeclaredMethod("run");

            method.invoke(target);

        }


        static class Proxy {

            public void run() {

                System.out.println("run");

            }

        }

    }


通过Java的反射机制,可以在运行期间调用对象的任何方法,如果大量使用这种方式进行调用,会有性能或内存隐患么?为了彻底了解方法的反射机制,只能从底层代码入手了。


Method获取


调用Class类的getDeclaredMethod可以获取指定方法名和参数的方法对象Method。


getDeclaredMethod

其中privateGetDeclaredMethods方法从缓存或JVM中获取该Class中申明的方法列表,searchMethods方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。


searchMethods

如果找到一个匹配的Method,则重新copy一份返回,即Method.copy()方法

所次每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象,如果需要频繁调用,最好把Method对象缓存起来。


privateGetDeclaredMethods


从缓存或JVM中获取该Class中申明的方法列表,实现如下:

其中reflectionData()方法实现如下:

这里有个比较重要的数据结构ReflectionData,用来缓存从JVM中读取类的如下属性数据:

从reflectionData()方法实现可以看出:reflectionData对象是SoftReference类型的,说明在内存紧张时可能会被回收,不过也可以通过-XX:SoftRefLRUPolicyMSPerMB参数控制回收的时机,只要发生GC就会将其回收,如果reflectionData被回收之后,又执行了反射方法,那只能通过newReflectionData方法重新创建一个这样的对象了,newReflectionData方法实现如下:

通过unsafe.compareAndSwapObject方法重新设置reflectionData字段;


在privateGetDeclaredMethods方法中,如果通过reflectionData()获得的ReflectionData对象不为空,则尝试从ReflectionData对象中获取declaredMethods属性,如果是第一次,或则被GC回收之后,重新初始化后的类属性为空,则需要重新到JVM中获取一次,并赋值给ReflectionData,下次调用就可以使用缓存数据了。


Method调用


获取到指定的方法对象Method之后,就可以调用它的invoke方法了,invoke实现如下:

应该注意到:这里的MethodAccessor对象是invoke方法实现的关键,一开始methodAccessor为空,需要调用acquireMethodAccessor生成一个新的MethodAccessor对象,MethodAccessor本身就是一个接口,实现如下:

在acquireMethodAccessor方法中,会通过ReflectionFactory类的newMethodAccessor创建一个实现了MethodAccessor接口的对象,实现如下:

在ReflectionFactory类中,有2个重要的字段:noInflation(默认false)和inflationThreshold(默认15),在checkInitted方法中可以通过-Dsun.reflect.inflationThreshold=xxx和-Dsun.reflect.noInflation=true对这两个字段重新设置,而且只会设置一次;


如果noInflation为false,方法newMethodAccessor都会返回DelegatingMethodAccessorImpl对象,DelegatingMethodAccessorImpl的类实现

其实,DelegatingMethodAccessorImpl对象就是一个代理对象,负责调用被代理对象delegate的invoke方法,其中delegate参数目前是NativeMethodAccessorImpl对象,所以最终Method的invoke方法调用的是NativeMethodAccessorImpl对象invoke方法,实现如下:

这里用到了ReflectionFactory类中的inflationThreshold,当delegate调用了15次invoke方法之后,如果继续调用就通过MethodAccessorGenerator类的generateMethod方法生成MethodAccessorImpl对象,并设置为delegate对象,这样下次执行Method.invoke时,就调用新建的MethodAccessor对象的invoke()方法了。


这里需要注意的是:


generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象,实现如下:

在ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象

这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。

© 著作权归作者所有

共有 人打赏支持
stars永恒
粉丝 9
博文 159
码字总数 198013
作品 0
大兴
后端工程师
私信 提问
111 多线程JUC包下代码分析

Java多线程系列目录(共43篇) AtomicLongFieldUpdater:通过反射+CAS实现对传入对象的指定long字段实现类似AtomicLong的操作 http://www.cnblogs.com/skywang12345/p/javathreadscategory.ht...

素雷
2017/10/31
0
0
模拟spring IOC 实现

利用java的反射和动态代理实现IOC 在Java中,其反射和动态代理机制极其强大,我们可以通过其反射机制在运行时获取信息。而代理是一种基本的设计模式,它是一种为了提供额外的或不同的操作而插...

candies
2014/03/02
0
0
老司机带你深入浅出Java反射

反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息 这里笔者就深入浅出总结下Java反射,...

小刀爱编程
2018/11/07
0
0
学Android开发,入门语言java知识点

Android是一种以Linux为基础的开源码操作系统,主要使用于便携设备,而linux是用c语言和少量汇编语言写成的,如果你想研究Android,就去学java语言吧。 Android开发入门教程 -Java语言,最差...

抉择很难
2015/12/11
185
0
深入分析Java ClassLoader原理

一、什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个...

5W1H-
2013/05/27
0
2

没有更多内容

加载失败,请刷新页面

加载更多

csapp 习题 - 如何实现异或 exclusive-or

阅读 csapp v3 时,练习题 2.13 很有意思。练习题描述如下。 位设置是对于参数 mask 中每一个为 1 的位,那么参数 x 中相应位则被设置为 1 ;位清除是对于参数 mask 中每一个为 1 的位,那么...

ylme
昨天
1
0
Amino——产品迭代

兴趣部落产品迭代 时间 版本号 更新内容 备注 2019年1月2日 v3.1.1 支持定制部落首页的内容tab,酋长可以将精华、相册、分类添加到部落首页啦。 支持申请酋长,酋长可以直接推送优质话题,快...

铸剑为犁413
昨天
0
0
IntelliJ IDEA 安装 Scala 插件

本页面中对在 IntelliJ 中安装 Scala 插件的步骤和方法进行了描述。 需要在 IntelliJ 安装 Scala 插件,你首先需要在你的计算机中安装 IntelliJ 。IntelliJ 的安装比较简单,一般按照步骤安装...

honeymose
昨天
0
0
JavaScript继承(二)——借用构造函数

JavaScript继承(一)——原型链中提出原型链继承的两个问题:一是原型的数据共享问题,二是创建子类型的实例时,不能向父类型的构造函数中传递参数。这两个问题的根源还是在于使用原型模式创...

Bob2100
昨天
1
0
day25:比较两个文件内容|杀进程|并发备份数据库|并发进程备份mysql库|监控全国CDN网站节点|

1、有两个文件 a.txt 和 b.txt,把 a.txt 中有的但 b.txt 中没有的行找出来,并写入列 c.txt,然后计算 c 的行数; 注释:比较两个文件两个文件不同,可以用的方法:diff md5sum grep -vf $1 $...

芬野de博客
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部