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 虚拟机》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。
关注本人公众号,第一时间获取最新文章发布,每日更新一篇技术文章。
07 JVM 是如何实现反射的的更多相关文章
- JVM类加载机制与反射-转
一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允 ...
- 《深入理解Java虚拟机》- JVM是如何实现反射的
Java反射学问很深,这里就浅谈吧.如果涉及到方法内联,逃逸分析的话,我们就说说是什么就好了.有兴趣的可以去另外看看,我后面可能也会写一下.(因为我也不会呀~) 一.Java反射是什么? 反射的核心是 ...
- Java jvm 类加载 反射
Java 底层 jvm,类加载,反射 Java语言是跨平台语言,一段java代码,经过编译成class文件后,能够在不同系统的服务器上运行:因为java语言中有虚拟机jvm,才有了跨平台,java为了 ...
- 深入理解Jvm 虚拟机
参考: 内存模型:https://blog.csdn.net/qq_34280276/article/details/52783096 类加载原理:https://nomico271.github.i ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- 代理、反射、注解、hook
代理 通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能. 代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用 这里使用到编程中的一个思想:不 ...
- 进阶Java编程(9)反射与类操作
1,反射获取类结构信息 在反射机制的处理过程之中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成结构操作,任何一个类的基本组成结构:父类(父接口).包.属性.方法(构造方法与普通方法). ...
- java反射(三)--反射与操作类
一.反射与操作类 在反射机制的处理过程之中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成的操作,任何一个类的基本组成结构:父类(父接口),包,属性,方法(构造方法,普通方法)--获取类的 ...
- Java反射详解:入门+使用+原理+应用场景
反射非常强大和有用,现在市面上绝大部分框架(spring.mybatis.rocketmq等等)中都有反射的影子,反射机制在框架设计中占有举足轻重的作用. 所以,在你Java进阶的道路上,你需要掌握好 ...
随机推荐
- python 学习之FAQ:文档内容写入报错
2017.3.29 FAQ 1. 文档内容写入报错 使用with open() as file: 写入文档时,出现'\xa9'特殊字符写入报错,通过print('\xa9')打印输出“©”. > ...
- 标准IO ——将A文件fpd第3个字节之后的内容复制到文件fps
/* *使用标准IO ——将A文件fpd第3个字节之后的内容复制到文件fps 流程: 1.创建两个流,链接目标文件和源文件 2.输入流的基准点偏移四个单位然后输入缓冲区 3.输出流读取缓冲区数据送入文 ...
- pta 编程题20 旅游规划
其它pta数据结构编程题请参见:pta 题目 这个最短路径问题只需要求两点之间的最短路径,因而在Dijikstra算法中当求出目标点的最短路径之后跳出循环即可. #include <iostre ...
- top命令交互快捷键
#toptop - :: up :, users, load average: 0.17, 0.12, 0.14 Tasks: total, running, sleeping, stopped, z ...
- 【洛谷1337】[JSOI2004] 吊打XXX(模拟退火经典题)
点此看题面 大致题意: 一个平面上有\(n\)个点,每个点有1个权值,现在要选择平面上的一个点,使这\(n\)个点的权值乘上到达选定点的距离之和最小. 模拟退火 我们可以用模拟退火来做这道题. 先将\ ...
- MFC-[转]基于MFC的ActiveX控件开发
作者:lidan | 出处:博客园 | 2012/3/13 16:10:34 | 阅读22次 ActiveX 控件是基于组件对象模型 (COM) 的可重用软件组件,广泛应用于桌面及Web应用中.在VC ...
- 什么是SAD,SAE,SATD,SSD,SSE,MAD,MAE,MSD,MSE?
SAD(Sum of Absolute Difference)=SAE(Sum of Absolute Error)即绝对误差和 SATD(Sum of Absolute Transformed Di ...
- java设计模式——单例模式(一)
一. 定义与类型 定义:保证一个类仅有一个实例,并提供一个全局访问点 类型:创建型 二. 适用场景 想确保任何情况下都绝对只用一个实例 三. 优缺点 优点: 在内存里只有一个实例,减少了内存开销 可以 ...
- java arraylist int[] 转换
double no=Double.valueOf("str");int num4=(int)no;double no1=Double.parseDouble("str&q ...
- java定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积
需求如下:(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积. (2)定义一个类PassObject,在类中定义一个方法pri ...