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 虚拟机》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

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

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

  1. JVM类加载机制与反射-转

    一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允 ...

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

    Java反射学问很深,这里就浅谈吧.如果涉及到方法内联,逃逸分析的话,我们就说说是什么就好了.有兴趣的可以去另外看看,我后面可能也会写一下.(因为我也不会呀~) 一.Java反射是什么? 反射的核心是 ...

  3. Java jvm 类加载 反射

    Java 底层 jvm,类加载,反射 Java语言是跨平台语言,一段java代码,经过编译成class文件后,能够在不同系统的服务器上运行:因为java语言中有虚拟机jvm,才有了跨平台,java为了 ...

  4. 深入理解Jvm 虚拟机

    参考: 内存模型:https://blog.csdn.net/qq_34280276/article/details/52783096 类加载原理:https://nomico271.github.i ...

  5. jvm内存溢出分析

    概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...

  6. 代理、反射、注解、hook

    代理 通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能. 代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用 这里使用到编程中的一个思想:不 ...

  7. 进阶Java编程(9)反射与类操作

    1,反射获取类结构信息 在反射机制的处理过程之中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成结构操作,任何一个类的基本组成结构:父类(父接口).包.属性.方法(构造方法与普通方法). ...

  8. java反射(三)--反射与操作类

    一.反射与操作类 在反射机制的处理过程之中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成的操作,任何一个类的基本组成结构:父类(父接口),包,属性,方法(构造方法,普通方法)--获取类的 ...

  9. Java反射详解:入门+使用+原理+应用场景

    反射非常强大和有用,现在市面上绝大部分框架(spring.mybatis.rocketmq等等)中都有反射的影子,反射机制在框架设计中占有举足轻重的作用. 所以,在你Java进阶的道路上,你需要掌握好 ...

随机推荐

  1. C#之MVC3继续整理问题

    1.注释验证[EmailAddress(ErrorMessage = "×")],用的MVC3框架,此处报错,找不到类“EmailAddress”,看到原文有using Syste ...

  2. [转贴] 2016一月12日起.NET 4, 4.5 and 4.5.1 停止安全更新、技术支持 or hotfix

    [转贴] 2016一月12日起.NET 4, 4.5 and 4.5.1 停止安全更新.技术支持 or hotfix https://www.dotblogs.com.tw/mis2000lab/20 ...

  3. JAVA设计模式初探之桥接模式

    生活中的一个例子:    拿汽车在路上行驶的来说.既有小汽车又有公共汽车,它们都不但能在市区中的公路上行驶,也能在高速公路上行驶.这你会发现,对于交通工具(汽车)有不同的类型,它们所行驶的环境(路)也 ...

  4. flex在众多手机浏览器上的兼容方案(亲测华为手机自带浏览器)

    如果项目使用构建工具,可加autoprefixer来处理,[autoprefixer使用指南] 纯手写css兼容代码,需给每个使用的属性加上属性前缀 /*display: flex;写法*/ span ...

  5. solr数据分片相关

    solr操作url 使用正常的core,使用命令生成coillection solr create_collection -c students2 -d ../server/solr/my/conf ...

  6. Restful API 的设计规范

    RESTful 是目前最流行的 API 设计规范,用于 Web 数据接口的设计.降低开发的复杂性,提高系统的可伸缩性. Restful API接口规范包括以下部分: 一.协议 API与用户的通信协议, ...

  7. java程序换图标

    ImageIcon img = new ImageIcon("D:\\mahou-in-action\\ShiJuanFenXi\\src\\zoom-in.png"); inst ...

  8. js点击拉拽轮播图pc端移动端适配

    <div class="content"> <button class="left">left</button> <b ...

  9. 1305: [CQOI2009]dance跳舞

    Time Limit: 5 Sec  Memory Limit: 162 MBSubmit: 4169  Solved: 1804[Submit][Status][Discuss] Descripti ...

  10. LGTB 学分块

    总时间限制:  10000ms 单个测试点时间限制:  1000ms 内存限制:  65536kB 描述 LGTB 最近在学分块,但是他太菜了,分的块数量太多他就混乱了,所以只能分成 3 块 今天他得 ...