JVM是如何处理反射的
反射实现1-调用本地方法
例:
1 // v0版本
2 import java.lang.reflect.Method;
3
4 public class Test {
5 public static void target(int i) {
6 new Exception("#" + i).printStackTrace();
7 }
8
9 public static void main(String[] args) throws Exception {
10 Class<?> klass = Class.forName("Test");
11 Method method = klass.getMethod("target", int.class);
12 method.invoke(null, 0);
13 }
14 }
java Test结果:
1 $ java Test
2 java.lang.Exception: #0
3 at Test.target(Test.java:6) // 4.最后到达目标方法
4 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
5 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) // 3.再然后进入本地实现(NativeMethodAccessorImpl)
6 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) // 2.然后进入委派实现(DelegatingMethodAccessorImpl)
7 at java.lang.reflect.Method.invoke(Method.java:498) // 1.先是调用了 Method.invoke
8 at Test.main(Test.java:12)
反射实现2-动态生成字节码
问:为什么反射调用还要采取委派实现作为中间层?直接交给本地实现不可以么?
答:其实,Java 的反射调用机制还设立了另一种动态生成字节码的实现(下称动态实现),直接使用 invoke 指令来调用目标方法。之所以采用委派实现,便是为了能够在本地实现以及动态实现中切换。
1 // 动态实现的伪代码,这里只列举了关键的调用逻辑,其实它还包括调用者检测、参数检测的字节码。
2 package jdk.internal.reflect;
3
4 public class GeneratedMethodAccessor1 extends ... {
5 @Overrides
6 public Object invoke(Object obj, Object[] args) throws ... {
7 Test.target((int) args[0]);
8 return null;
9 }
10 }
动态实现和本地实现相比,其运行效率要快上 20 倍 [2] 。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍 [3]。
考虑到许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15(可以通过 -Dsun.reflect.inflationThreshold= 来调整),当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程我们称之为 Inflation(翻译:膨胀、通货膨胀)。
反射调用的 Inflation 机制是可以通过参数(-Dsun.reflect.noInflation=true)来关闭的。这样一来,在反射调用一开始便会直接生成动态实现,而不会使用委派实现或者本地实现
例子:
1 // v1版本
2 import java.lang.reflect.Method;
3
4 public class Test {
5 public static void target(int i) {
6 new Exception("#" + i).printStackTrace();
7 }
8
9 public static void main(String[] args) throws Exception {
10 Class<?> klass = Class.forName("Test");
11 Method method = klass.getMethod("target", int.class);
12 for (int i = 0; i < 20; i++) {
13 method.invoke(null, i);
14 }
15 }
16 }
结果:
1 $ java -verbose:class Test // 使用-verbose:class打印加载的类
2 [Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
3 [Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
4 [Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
5 ......
6 [Loaded java.lang.Throwable$PrintStreamOrWriter from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
7 [Loaded java.lang.Throwable$WrappedPrintStream from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
8 [Loaded java.util.IdentityHashMap from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
9 [Loaded java.util.IdentityHashMap$KeySet from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
10 java.lang.Exception: #0
11 at Test.target(Test.java:6)
12 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
13 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
14 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
15 at java.lang.reflect.Method.invoke(Method.java:498)
16 at Test.main(Test.java:13)
17 oke(Method.java:498)
18 ......
19 java.lang.Exception: #14
20 at Test.target(Test.java:6)
21 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
22 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
23 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
24 at java.lang.reflect.Method.invoke(Method.java:498)
25 at Test.main(Test.java:13)
26 [Loaded sun.reflect.ClassFileConstants from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
27 [Loaded sun.reflect.AccessorGenerator from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
28 [Loaded sun.reflect.MethodAccessorGenerator from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
29 [Loaded sun.reflect.ByteVectorFactory from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
30 [Loaded sun.reflect.ByteVector from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
31 [Loaded sun.reflect.ByteVectorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
32 [Loaded sun.reflect.ClassFileAssembler from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
33 [Loaded sun.reflect.UTF8 from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
34 [Loaded sun.reflect.Label from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
35 [Loaded sun.reflect.Label$PatchInfo from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
36 [Loaded java.util.ArrayList$Itr from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
37 [Loaded sun.reflect.MethodAccessorGenerator$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
38 [Loaded sun.reflect.ClassDefiner from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
39 [Loaded sun.reflect.ClassDefiner$1 from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
40 [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
41 java.lang.Exception: #15
42 at Test.target(Test.java:6)
43 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
44 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) // 本地方法调用
45 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
46 at java.lang.reflect.Method.invoke(Method.java:498)
47 at Test.main(Test.java:13)
48 [Loaded java.util.concurrent.ConcurrentHashMap$ForwardingNode from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
49 java.lang.Exception: #16
50 at Test.target(Test.java:6)
51 at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) // 从第16次开始,切换到字节码调用(即动态实现)
52 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
53 at java.lang.reflect.Method.invoke(Method.java:498)
54 at Test.main(Test.java:13)
55 ......
56 java.lang.Exception: #19
57 at Test.target(Test.java:6)
58 at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
59 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
60 at java.lang.reflect.Method.invoke(Method.java:498)
61 at Test.main(Test.java:13)
62 [Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
63 [Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar]
反射性能开销
下面,我们便来拆解反射调用的性能开销。
在刚才的例子中,我们先后进行了 Class.forName,Class.getMethod 以及 Method.invoke 三个操作。其中,Class.forName 会调用本地方法(Java和C++的相互转换,非常耗时),Class.getMethod 则会遍历该类的公有方法。如果没有匹配到,它还将遍历父类的公有方法。可想而知,这两个操作都非常费时。
值得注意的是,以 getMethod 为代表的查找方法操作,会返回查找得到结果的一份拷贝。因此,我们应当避免在热点代码中使用返回 Method 数组的 getMethods 或者 getDeclaredMethods 方法,以减少不必要的堆空间消耗。
在实践中,我们往往会在应用程序中缓存 Class.forName 和 Class.getMethod 的结果。因此,下面我就只关注反射调用本身的性能开销。
例:
1 // v2版本
2 mport java.lang.reflect.Method;
3
4 public class Test {
5 public static void target(int i) {
6 // 空方法
7 }
8
9 public static void main(String[] args) throws Exception {
10 Class<?> klass = Class.forName("Test");
11 Method method = klass.getMethod("target", int.class);
12
13 long current = System.currentTimeMillis();
14 for (int i = 1; i <= 2_000_000_000; i++) {
15 if (i % 100_000_000 == 0) {
16 long temp = System.currentTimeMillis();
17 System.out.println(temp - current);
18 current = temp;
19 }
20
21 method.invoke(null, 128);
22 }
23 }
24 }
反射调用之前,字节码都做了什么
59: aload_2 // 加载Method对象
60: aconst_null // 反射调用的第一个参数null
61: iconst_1
62: anewarray Object // 生成一个长度为1的Object数组
65: dup
66: iconst_0
67: sipush 128
70: invokestatic Integer.valueOf // 将128自动装箱成Integer
73: aastore // 存入Object数组中
74: invokevirtual Method.invoke // 反射调用
这里我截取了循环中反射调用编译而成的字节码。
可以看到,这段字节码除了反射调用外,还额外做了两个操作。
第一,由于 Method.invoke 是一个变长参数方法,在字节码层面它的最后一个参数会是 Object 数组(感兴趣的同学私下可以用 javap 查看)。Java 编译器会在方法调用处生成一个长度为传入参数数量的 Object 数组,并将传入参数一一存储进该数组中。
第二,由于 Object 数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱。
这两个操作除了带来性能开销外,还可能占用堆内存,使得 GC 更加频繁。(如果你感兴趣的话,可以用虚拟机参数 -XX:+PrintGC 试试。)
另外,有些情况下 反射调用能够变得非常快(和非反射没什么区别),主要是因为即时编译器中的方法内联。在关闭了 Inflation 的情况下,内联的瓶颈在于 Method.invoke 方法中对 MethodAccessor.invoke 方法的调用。
我会在后面的文章中介绍方法内联的具体实现,这里先说个结论:在生产环境中,我们往往拥有多个不同的反射调用,对应多个 GeneratedMethodAccessor,也就是动态实现。由于 Java 虚拟机的关于上述调用点的类型 profile(注:对于 invokevirtual 或者 invokeinterface,Java 虚拟机会记录下调用者的具体类型,我们称之为类型 profile)无法同时记录这么多个类,因此可能造成所测试的反射调用没有被内联的情况。
例:
1 // v5版本
2 import java.lang.reflect.Method;
3
4 public class Test {
5 public static void target(int i) {
6 // 空方法
7 }
8
9 public static void main(String[] args) throws Exception {
10 Class<?> klass = Class.forName("Test");
11 Method method = klass.getMethod("target", int.class);
12 method.setAccessible(true); // 关闭权限检查
13 polluteProfile(); // 这个方法里面放射调用另外两个方法,导致上述的target方法的内联失效
14
15 long current = System.currentTimeMillis();
16 for (int i = 1; i <= 2_000_000_000; i++) {
17 if (i % 100_000_000 == 0) {
18 long temp = System.currentTimeMillis();
19 System.out.println(temp - current);
20 current = temp;
21 }
22
23 method.invoke(null, 128);
24 }
25 }
26
27 public static void polluteProfile() throws Exception {
28 Method method1 = Test.class.getMethod("target1", int.class);
29 Method method2 = Test.class.getMethod("target2", int.class);
30 for (int i = 0; i < 2000; i++) {
31 method1.invoke(null, 0);
32 method2.invoke(null, 0);
33 }
34 }
35 public static void target1(int i) { }
36 public static void target2(int i) { }
37 }
在上面的 v5 版本中,我在测试循环之前调用了 polluteProfile 的方法。该方法将反射调用另外两个方法,并且循环上 2000 遍。而测试循环则保持不变。测得的结果约为基准的 6.7 倍。也就是说,只要误扰了 Method.invoke 方法的类型 profile,性能开销便会从 1.3 倍上升至 6.7 倍。
今天的实践环节,你可以将最后一段代码中 polluteProfile 方法的两个 Method 对象,都改成获取名字为“target”的方法。请问这两个获得的 Method 对象是同一个吗(==)?他们 equal 吗(.equals(…))?对我们的运行结果有什么影响?
解答:https://blog.csdn.net/ti_an_di/article/details/82049230
显然,我们是不同的引用,但它们指向的值是相等的,即method1==method2 为false,method1.equals(method2)为true。结果就是又会恢复到以前的运行速率,因为类型profile不会被target1和target2占用了。
附录:反射 API 简介
通常来说,使用反射 API 的第一步便是获取 Class 对象。在 Java 中常见的有这么三种。
- 使用静态方法 Class.forName 来获取。
- 调用对象的 getClass() 方法。
- 直接用类名 +“.class”访问。对于基本类型来说,它们的包装类型(wrapper classes)拥有一个名为“TYPE”的 final 静态字段,指向该基本类型对应的 Class 对象。
例如,Integer.TYPE 指向 int.class。对于数组类型来说,可以使用类名 +“[ ].class”来访问,如 int[ ].class。
除此之外,Class 类和 java.lang.reflect 包中还提供了许多返回 Class 对象的方法。例如,对于数组类的 Class 对象,调用 Class.getComponentType() 方法可以获得数组元素的类型。
一旦得到了 Class 对象,我们便可以正式地使用反射功能了。下面我列举了较为常用的几项。
- 使用 newInstance() 来生成一个该类的实例。它要求该类中拥有一个无参数的构造器。
- 使用 isInstance(Object) 来判断一个对象是否该类的实例,语法上等同于 instanceof 关键字(JIT 优化时会有差别,我会在本专栏的第二部分详细介绍)。
- 使用 Array.newInstance(Class,int) 来构造该类型的数组。
- 使用 getFields()/getConstructors()/getMethods() 来访问该类的成员。除了这三个之外,Class 类还提供了许多其他方法,详见[4]。需要注意的是,方法名中带 Declared 的不会返回父类的成员,但是会返回私有成员;而不带 Declared 的则相反。
当获得了类成员之后,我们可以进一步做如下操作。
- 使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制。
- 使用 Constructor.newInstance(Object[]) 来生成该类的实例。
- 使用 Field.get/set(Object) 来访问字段的值。
- 使用 Method.invoke(Object, Object[]) 来调用方法。
有关反射 API 的其他用法,可以参考 reflect 包的 javadoc [5] ,这里就不详细展开了。
JVM是如何处理反射的的更多相关文章
- 【JVM虚拟机】(9)-- JVM是如何处理异常的
[JVM虚拟机](9)-- JVM是如何处理异常的 上篇博客我们简单说过异常信息是存放在属性表集合中的Code属性表里,那么这篇博客就单独讲Code属性表中的exception_table. 在讲之前 ...
- JVM是如何处理异常的
JVM处理异常 异常处理的两大组成要素是抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移. 抛出异常可分为显式和隐式两种.显式抛异常的主体是应用程序,指的是在程序中使用throw关键字,手 ...
- 06 JVM 是如何处理异常的
在 JAVA 中,异常处理的方式主要是抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移. 抛出异常可以分为显示和隐式两种.显示抛出异常的主体是应用程序,它指的是在程序中使用 throw 关 ...
- 有关JVM处理Java数组方法的思考
在Java中,获取数组的长度和String的长度是两种不同的方法,这引起了本文作者的一番思考.本文从JVM的角度,探讨了Java数组在JVM中是什么对象,有哪些成员,以及声明方法. 作者:jarfie ...
- JVM原理和优化
JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...
- jvm 原理和优化
在csdn 上看到的,觉得很好,收藏了: 原博文地址: 濤子 http://blog.csdn.net/ning109314/article/details/10411495/ JVM工作原理和特点主 ...
- JBOSS最大连接数配置和jvm内存配置
一.调整JBOSS最大连接数. 配置deploy/jboss-web.deployer/server.xml文件 . <Connector port="80 ...
- JVM知识学习与巩固
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 我们运行和调 ...
- JVM 进行线程同步背后的原理
前言 所有的 Java 程序都会被翻译为包含字节码的 class 文件,字节码是 JVM 的机器语言.这篇文章将阐述 JVM 是如何处理线程同步以及相关的字节码. 线程和共享数据 Java 的一个优点 ...
- Java编程 的动态性,第 2部分: 引入反射--转载
在“ Java编程的动态性,第1部分,”我为您介绍了Java编程类和类装入.该篇文章介绍了一些Java二进制类格式的相关信息.这个月我将阐述使用Java反射API来在运行时接入和使用一些相同信息的基础 ...
随机推荐
- 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(10)- 从Serial NAND启动
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT1xxx系列MCU的Serial NAND启动. 最近越来越多的客户在咨询 i.MXRT1xxx 从 Serial N ...
- Mysql生成测试数据函数
1.查看设置是否允许创建函数系统参数 show variables like 'log_bin_trust_function_creators'; 2.临时设置允许创建函数系统参数 set globa ...
- Flutter系列文章-Flutter进阶2
这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由.状态管理.异步处理.HTTP请求和Rest API,以及数据持久化.让我们逐个介绍这些主题. 1.导航和路由 在 Flutter ...
- 《高级程序员 面试攻略 》rabitmq rcoketmq kafka的区别 和应用场景
RabbitMQ.RocketMQ 和 Kafka 都是流行的消息中间件系统,用于实现分布式应用程序之间的异步通信.虽然它们都有类似的目标,但在设计和应用场景上存在一些区别. 1. RabbitMQ( ...
- LDAP:如何在windows系统下安装LDAP及连接测试
1.LDAP介绍 LDAP是一个基于X.500标准的轻量目录访问协议,与X.500不同,LDAP协议支持TCP/IP连接.全称为Lightweight Directory Access Protoco ...
- 揭秘ChatGPT,如何打造自己的自定义指令
一.ChatGPT-0720更新 又在深夜,正要打开ChatGPT官网测试下pdf对话功能,发现ChatGPT又有更新.本次更新总结有2点: 1.对于Plus用户,GPT-4的使用限额从25条/3h提 ...
- wget 命令的使用:HTTP文件下载、FTP文件下载--九五小庞
1. wget 命令简介与安装wget是用于在命令行终端下载网络文件的开源免费的命令工具,支持 HTTP/HTTPS.FTP/FTPS 协议的下载.wget 与 curl 相似,curl 可以理解为是 ...
- 正则表达式快速入门三: python re module + regex 匹配示例
使用 Python 实现不同的正则匹配(从literal character到 其他常见用例) reference python regular expression tutorial 目录 impo ...
- windows 网络模拟工具分享
[下载地址] Releases · jagt/clumsy · GitHub [介绍] 无需安装 无需篡改和代理 系统级限制,不针对单个程序,但可以针对单个IP 离线也可以限制,随停随用 界面简单 [ ...
- EtherCAT转Modbus网关用Modbus Slave模拟从站配置案例
EtherCAT转Modbus网关用Modbus Slave模拟从站配置案例 兴达易控EtherCAT到Modbus网关可以用作Modbus从站的配置.EtherCAT到Modbus网关允许Modbu ...