反射实现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 中常见的有这么三种。

  1. 使用静态方法 Class.forName 来获取。
  2. 调用对象的 getClass() 方法。
  3. 直接用类名 +“.class”访问。对于基本类型来说,它们的包装类型(wrapper classes)拥有一个名为“TYPE”的 final 静态字段,指向该基本类型对应的 Class 对象。

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

  除此之外,Class 类和 java.lang.reflect 包中还提供了许多返回 Class 对象的方法。例如,对于数组类的 Class 对象,调用 Class.getComponentType() 方法可以获得数组元素的类型。

一旦得到了 Class 对象,我们便可以正式地使用反射功能了。下面我列举了较为常用的几项。

  1. 使用 newInstance() 来生成一个该类的实例。它要求该类中拥有一个无参数的构造器。
  2. 使用 isInstance(Object) 来判断一个对象是否该类的实例,语法上等同于 instanceof 关键字(JIT 优化时会有差别,我会在本专栏的第二部分详细介绍)。
  3. 使用 Array.newInstance(Class,int) 来构造该类型的数组。
  4. 使用 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是如何处理反射的的更多相关文章

  1. 【JVM虚拟机】(9)-- JVM是如何处理异常的

    [JVM虚拟机](9)-- JVM是如何处理异常的 上篇博客我们简单说过异常信息是存放在属性表集合中的Code属性表里,那么这篇博客就单独讲Code属性表中的exception_table. 在讲之前 ...

  2. JVM是如何处理异常的

    JVM处理异常 异常处理的两大组成要素是抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移. 抛出异常可分为显式和隐式两种.显式抛异常的主体是应用程序,指的是在程序中使用throw关键字,手 ...

  3. 06 JVM 是如何处理异常的

    在 JAVA 中,异常处理的方式主要是抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移. 抛出异常可以分为显示和隐式两种.显示抛出异常的主体是应用程序,它指的是在程序中使用 throw 关 ...

  4. 有关JVM处理Java数组方法的思考

    在Java中,获取数组的长度和String的长度是两种不同的方法,这引起了本文作者的一番思考.本文从JVM的角度,探讨了Java数组在JVM中是什么对象,有哪些成员,以及声明方法. 作者:jarfie ...

  5. JVM原理和优化

    JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...

  6. jvm 原理和优化

    在csdn 上看到的,觉得很好,收藏了: 原博文地址: 濤子 http://blog.csdn.net/ning109314/article/details/10411495/ JVM工作原理和特点主 ...

  7. JBOSS最大连接数配置和jvm内存配置

    一.调整JBOSS最大连接数. 配置deploy/jboss-web.deployer/server.xml文件 .       <Connector         port="80 ...

  8. JVM知识学习与巩固

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. 我们运行和调 ...

  9. JVM 进行线程同步背后的原理

    前言 所有的 Java 程序都会被翻译为包含字节码的 class 文件,字节码是 JVM 的机器语言.这篇文章将阐述 JVM 是如何处理线程同步以及相关的字节码. 线程和共享数据 Java 的一个优点 ...

  10. Java编程 的动态性,第 2部分: 引入反射--转载

    在“ Java编程的动态性,第1部分,”我为您介绍了Java编程类和类装入.该篇文章介绍了一些Java二进制类格式的相关信息.这个月我将阐述使用Java反射API来在运行时接入和使用一些相同信息的基础 ...

随机推荐

  1. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(10)- 从Serial NAND启动

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT1xxx系列MCU的Serial NAND启动. 最近越来越多的客户在咨询 i.MXRT1xxx 从 Serial N ...

  2. Mysql生成测试数据函数

    1.查看设置是否允许创建函数系统参数 show variables like 'log_bin_trust_function_creators'; 2.临时设置允许创建函数系统参数 set globa ...

  3. Flutter系列文章-Flutter进阶2

    这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由.状态管理.异步处理.HTTP请求和Rest API,以及数据持久化.让我们逐个介绍这些主题. 1.导航和路由 在 Flutter ...

  4. 《高级程序员 面试攻略 》rabitmq rcoketmq kafka的区别 和应用场景

    RabbitMQ.RocketMQ 和 Kafka 都是流行的消息中间件系统,用于实现分布式应用程序之间的异步通信.虽然它们都有类似的目标,但在设计和应用场景上存在一些区别. 1. RabbitMQ( ...

  5. LDAP:如何在windows系统下安装LDAP及连接测试

    1.LDAP介绍 LDAP是一个基于X.500标准的轻量目录访问协议,与X.500不同,LDAP协议支持TCP/IP连接.全称为Lightweight Directory Access Protoco ...

  6. 揭秘ChatGPT,如何打造自己的自定义指令

    一.ChatGPT-0720更新 又在深夜,正要打开ChatGPT官网测试下pdf对话功能,发现ChatGPT又有更新.本次更新总结有2点: 1.对于Plus用户,GPT-4的使用限额从25条/3h提 ...

  7. wget 命令的使用:HTTP文件下载、FTP文件下载--九五小庞

    1. wget 命令简介与安装wget是用于在命令行终端下载网络文件的开源免费的命令工具,支持 HTTP/HTTPS.FTP/FTPS 协议的下载.wget 与 curl 相似,curl 可以理解为是 ...

  8. 正则表达式快速入门三: python re module + regex 匹配示例

    使用 Python 实现不同的正则匹配(从literal character到 其他常见用例) reference python regular expression tutorial 目录 impo ...

  9. windows 网络模拟工具分享

    [下载地址] Releases · jagt/clumsy · GitHub [介绍] 无需安装 无需篡改和代理 系统级限制,不针对单个程序,但可以针对单个IP 离线也可以限制,随停随用 界面简单 [ ...

  10. EtherCAT转Modbus网关用Modbus Slave模拟从站配置案例

    EtherCAT转Modbus网关用Modbus Slave模拟从站配置案例 兴达易控EtherCAT到Modbus网关可以用作Modbus从站的配置.EtherCAT到Modbus网关允许Modbu ...