Java方法反射实现原理详解

所属分类: 软件编程 / java 阅读数: 23
收藏 0 赞 0 分享

博主说:Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。在本文中,占小狼分析了 Java 反射机制的实现原理(源码),感兴趣的同学可以通过阅读本文花上几分钟了解了解。

正文

kava

方法反射实例

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

getDeclaredMethod

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

searchMethods

searchMethods

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

methodcopy

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

privateGetDeclaredMethods

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

privateGetDeclaredMethods

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

reflectionData

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

JVM

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

newReflectionData

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

Method 调用

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

invoke

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

MethodAccessor

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

newMethodAccessor

在ReflectionFactory类中,有 2 个重要的字段:noInflation(默认false)和inflationThreshold(默认15),在checkInitted方法中可以通过-Dsun.reflect.inflationThreshold=xxx和-Dsun.reflect.noInflation=true对这两个字段重新设置,而且只会设置一次;如果noInflation为false,方法newMethodAccessor都会返回DelegatingMethodAccessorImpl对象,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 对象,实现如下:

return

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

DelegatingClassLoader

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

Java的面向对象编程基本概念学习笔记整理

这篇文章主要介绍了Java的面向对象编程基本概念学习笔记整理,包括类与方法以及多态等支持面向对象语言中的重要特点,需要的朋友可以参考下
收藏 0 赞 0 分享

Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法

这篇文章主要介绍了Eclipse下编写java程序突然不会自动生成R.java文件和包的解决办法 的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

基于Java实现杨辉三角 LeetCode Pascal's Triangle

这篇文章主要介绍了基于Java实现杨辉三角 LeetCode Pascal's Triangle的相关资料,需要的朋友可以参考下
收藏 0 赞 0 分享

Java中Spring获取bean方法小结

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,如何在程序中获取Spring配置的bean呢?下面通过本文给大家介绍Java中Spring获取bean方法小结,对spring获取bean方法相关知识感兴趣的朋友一起学习吧
收藏 0 赞 0 分享

如何计算Java对象占用了多少空间?

在Java中没有sizeof运算符,所以没办法知道一个对象到底占用了多大的空间,但是在分配对象的时候会有一些基本的规则,我们根据这些规则大致能判断出来对象大小,需要的朋友可以参考下
收藏 0 赞 0 分享

剖析Java中的事件处理与异常处理机制

这篇文章主要介绍了Java中的事件处理与异常处理机制,讲解Java是如何对事件或者异常作出响应以及定义异常的一些方法,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Java的Struts2框架的结构及其数据转移方式

这篇文章主要介绍了详解Java的Struts2框架的结构及其数据转移方式,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

Java封装好的mail包发送电子邮件的类

本文给大家分享了2个java封装好的mail包发送电子邮件的类,并附上使用方法,小伙伴们可以根据自己的需求自由选择。
收藏 0 赞 0 分享

在Java的Struts中判断是否调用AJAX及用拦截器对其优化

这篇文章主要介绍了在Java的Struts中判断是否调用AJAX及用拦截器对其优化的方法,Struts框架是Java的SSH三大web开发框架之一,需要的朋友可以参考下
收藏 0 赞 0 分享

java多线程Future和Callable类示例分享

JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。今天我们就来研究下Future和Callab
收藏 0 赞 0 分享
查看更多