07 JVM 是如何实现反射的

Java 中的反射

反射是 Java 语言的一个相当重要的特性,它允许正在运行的 Java 程序观测,甚至是修改程序的动态行为。

我们可以通过 Class 对象枚举该类中的所有方法,还可以通过 Method.SetAccessible 让过 Java 语言的访问权限,在私有方法所在类之外的地方调用该方法。

反射在 Java 中的引用十分广泛。日常我们用的 Java 继承开发工具 IDE 便运用了这一功能。比如,敲下点号时,IDE便会根据点号之前的内容动态的展示可以访问的字段或者方法。Java 调试器,能够在调试过程中枚举某一对象所有字段的值。当然,这些功能的实现也用到了语法树。在 Web 开发中,我们接触到的各种通用框架。为了保证框架的可扩展性,旺旺借助 Java 的反射机制,根据配置文件加载不同的类。例如 Spring 框架的以来反转 IOC。

反射调用的实现

首先,看一下 Method.invoke 的实现

public final class Method extends Executable {
  ...
  public Object invoke(Object obj, Object... args) throws ... {
    ... // 权限检查
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
      ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
  }
}

上面代码中,invoke 方法实际上委派给了 MethodAccessor 来处理。MethodAccessor 是一个接口,有两个具体的实现。一个是通过本地方法来实现反射调用,另一个则使用了委派模式。

每个 Method 实例第一次反射调用都会生成一个委派实现,它所委派的具体实现便是一个本地实现。在反射过程中,反射调用先是调用了 Method.invoke,然后进入委派实现 DelegatingMethodAccessorlmpl,再然后进入本地实现 NativeMethodAccessorlmpl,最后到达目标方法。

有个疑问,为什么反射调用还要采取委托实现作为中间层,为何不直接交给本地实现?

其实,Java 的反射调用机制还设立了另一种动态生成字节码的实现(下称动态实现)来直接使用 invoke 指令来调用目标方法。之所以采用委派实现,是为了能够在本地实现和动态实现中切换。动态实现和本地实现相比,运行效率更高。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,单由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要更快。

实际中,许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15,当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程叫 inflation。

反射调用的 inflation 机制是可以通过参数(-Dsun.reflect.noinflation = true)来关闭的。这样一来,在反射调用一开始便会直接生成动态实现,而不会使用委派实现或者本地实现。

反射调用的开销

在反射中 Class.forName,Class.getMethod 以及 Method.invoke 这三个方法,Class.forName 会调用本地方法,Class.getMethod 会遍历该类的共有方法。如果没有匹配,则会遍历父类的方法。在以 getMethod 为代表的查找方法操作中,会返回查找得到结果的一份拷贝。因此,应该避免在热点代码中使用返回 Method 数组的 getMethods 或者 getDeclaredMethods 方法,以减少不必要的堆空间消耗。实际开发中,我们也往往会缓存 Class.forName,Class.getMethod 的结果,因此,下面只关注反射调用本身的性能开销。

第一,Method.invoke 中的第二个参数是一个可以变长度的 Object 数组,数组中存放的都是对象类型。如果我们存入参数是基本类型,可以提前装箱,减少性能损耗。

第二,可以关闭反射调用的 inflation 机制,从而取消委派实现,并且直接使用动态实现。

第三,每次反射调用都会检查目标方法的权限,而这个检查同样可以在 Java 代码里关闭。

反射 API 简介

使用反射 API 第一步便是获取 Class 对象。获取对象有以下三种方式:

1:使用静态方法 Class.forName 来获取

2:调用对象的 getClass 方法

3:直接使用 类名+“.class” 访问。对于基本类型来说,他们的包装类型拥有一个名为 "TYPE" 的 final 静态字段,指向该基本类型对应的 Class 对象。

例如,Integer.TYPE 指向 int.class。对于数组类型来说,可以使用 类名+[].class 来访问,如 int[].class。

除此之外,Class 类和 java.lang.reflect 包中还提供了许多返回 Class 对象的方法。

获得 Class 对象之后,就可以正式使用反射功能了。下列为常用的几项:

1:使用 newInstance() 生成一个该类的实例。该类必须有一个无参构造函数

2:使用 isInstance(Object) 判断一个对象是否是该类的实例,语法上等同于 instanceof。

3:使用 Array.newInstance(Class,int) 来构造该类型的数组。

4:使用 getFields()/getConstructors()/getMethods() 来访问该类的成员。

问答

Q:当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码...

动态生成发生在第15次(从0开始数的话),所以第15次比较耗时。

Q:什么是 inflation 机制

反射的inflation机制是当反射被频繁调用时,动态生成一个类来做直接调用的机制,可以加速反射调用

总结

本文创作灵感来源于 极客时间 郑雨迪老师的《深入拆解 Java 虚拟机》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

关注本人公众号,第一时间获取最新文章发布,每日更新一篇技术文章。

原文地址:https://www.cnblogs.com/yuepenglei/p/10301213.html

时间: 2024-10-10 10:30:55

07 JVM 是如何实现反射的的相关文章

《深入理解Java虚拟机》- JVM是如何实现反射的

Java反射学问很深,这里就浅谈吧.如果涉及到方法内联,逃逸分析的话,我们就说说是什么就好了.有兴趣的可以去另外看看,我后面可能也会写一下.(因为我也不会呀~) 一.Java反射是什么? 反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁. 反射是由类开始的,从class对象中,我们可以获得有关该类的全部成员的完整列表:可以找出该类的所有类型.类自身信息. 二.反射的一些应用 1.java集成开发环境,每当我们敲入点号时,IDE便会根

代理、反射、注解、hook

代理 通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能. 代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用 这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法. 动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强.所有使用装饰者模式的案例都可以使用动态代理来替换. /** * subject(抽象主题角色): * 真实主题与代理主题的共同接口. */ interf

Java注解介绍

原文链接: Java Annotations: An Introduction原文日期: 2005年10月14日翻译日期: 2014年07月20日翻译人员: 铁锚 翻译完后,感觉这篇文章是不是在http://www.developer.com被挖坟了? Java注解介绍 基于注解(Annotation-based)的Java开发无疑是最新的开发趋势.[译者注: 这是05年的文章,在2014年,毫无疑问,多人合作的开发,使用注解变成很好的合作方式,相互之间的影响和耦合可以很低]. 基于注解的开发将

蚂蚁课堂(每特学院)-2期

0001-蚂蚁课堂(每特学院)-2期-多线程快速入门 第01节.线程与进程的区别 第02节.为什么要用到多线程 第03节.多线程应用场景 第04节.使用继承方式创建线程 第05节.使用Runnable接口方式创建线程 第06节.使用匿名内部类方式创建线程 第07节.多线程常用api 第08节.守护线程与非守护线程 第09节.多线程几种状态 第10节.join方法介绍 第11节.t1.t2.t3执行顺序面试题讲解 第12节.使用多线程分批处理信息 资料+源码.rar 0002-蚂蚁课堂(每特学院)

java 开发过程,各种各样的注解

@Retention Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值: 1.RetentionPolicy.SOURCE -- 这种类型的Annotations只在源代码级别保留,编译时就会被忽略 2.RetentionPolicy.CLASS -- 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略 3.RetentionPolicy.RUNTIME -- 这种类型的Annotations将被JVM保留,所以他们能在运行时被

Retention、Documented、Inherited三种注解

Retention注解 Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JV

Android中ViewMapping注解

1.Java.lang包中常用的注解有@Override,@Deprected(已经废弃),@SupressWarning(屏蔽掉一些警告.)我们可以自定义注解. 2.Java注解之@Retention,@Documented,@Inherited. Retention注解,保留注解说明,这种类型的猪儿会被保留到哪个阶段,有三个值:RetentionPolicy.SOURCE(只有源码级别保留,编译时被忽略),RetentionPolicy.CLASS(class文件中保留,Jvm被忽略),Re

第17篇-JAVA Annotation 注解

第17篇-JAVA Annotation 注解 每篇一句 :真的努力后你会发现自己要比想象的优秀很多 初学心得: 怀着一颗奋斗不息的心,一切困苦艰辛自当迎刃而解 (笔者:JEEP/711)[JAVA笔记 | 时间:2017-05-17| JAVA Annotation注解 ] 1.什么是注解(Annotation) Annotation 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行.在Java技术

【Spark2.0源码学习】-6.Client启动

Client作为Endpoint的具体实例,下面我们介绍一下Client启动以及OnStart指令后的额外工作 一.脚本概览 下面是一个举例: /opt/jdk1.7.0_79/bin/java -cp /opt/spark-2.1.0/conf/:/opt/spark-2.1.0/jars/*:/opt/hadoop-2.6.4/etc/hadoop/ -Xmx1g -XX:MaxPermSize=256m org.apache.spark.deploy.SparkSubmit --maste