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

一、Java反射是什么?

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

反射是由类开始的,从class对象中,我们可以获得有关该类的全部成员的完整列表;可以找出该类的所有类型、类自身信息。

二、反射的一些应用

1、java集成开发环境,每当我们敲入点号时,IDE便会根据点号前的内容,动态展示可以访问的字段和方法。

2、java调试器,它能够在调试过程中枚举某一对象所有字段的值。

3、web开发中,我们经常接触到各种配置的通用框架。为保证框架的可扩展性,他往往借助java的反射机制。例如Spring框架的依赖反转(IOC)便是依赖于反射机制。

三、Java反射的实现

1. Java反射使用的api(列举部分,具体在rt.jar包的java.lang.reflect.*)中

列举Class.java中的一些方法。这些都很常用,比如在你尝试编写一个mvc框架的时候,就可以参照这个类里面的方法,再结合一些Servlet的api就实现一个简单的框架。

2.代码实现

2.1代码实现的目的:说明反射调用是有两种方式,一种是本地实现,另一种是委派实现。

这里围绕Method.invoke方法展开。查看invoke()源码:

 public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}

说明:invoke()是有MethodAccessor接口实现的,这个接口有俩实现:

一个是使用委派模式的“委派实现”,一个是通过本地方法调用来实现反射调用的“本地实现”。

这两种实现不是独立的,而是相互协作的。下面,用代码让大家看一下具体操作。

Java代码:

public class InvokeDemo {
public static void target(int i){
new Exception("#"+i).printStackTrace();
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> invokeDemo1 = Class.forName("com.example.demo.invoke_demo.InvokeDemo");
Method method1 = invokeDemo1.getMethod("target", int.class);
method1.invoke(null,0);
}
}

运行之后,便可以在异常栈中查找方法调用的路线:

java.lang.Exception: #0
at com.example.demo.invoke_demo.InvokeDemo.target(InvokeDemo.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.example.demo.invoke_demo.InvokeDemo.main(InvokeDemo.java:15)

这里,我们会看到,invoke方法是先调用委派实现,然后再将请求传到本地方法实现的,最后在传到目标方法使用。

为什么要这样做呢?为什么不直接调用本地方法呢?

其实,Java的反射调用机制还设立了另一种动态生成字节码的实现(“动态实现”),直接使用invoke指令来调用目标方法。之所以采用委派实现,便是为了能够在“本地实现”和动态实现之间来回切换。(但是,动态实现貌似并没有开源

动态实现与本地实现的区别在于,反射代码段重复运行15次以上就会使用动态实现,15次以下就使用本地实现。下面是重复这个代码的控制台输出的第#14、#15、#16段异常:

Class<?> invokeDemo1 = Class.forName("com.example.demo.invoke_demo.InvokeDemo");
Method method1 = invokeDemo1.getMethod("target", int.class);
method1.invoke(null,0);

控制台:

java.lang.Exception: #15
at com.example.demo.invoke_demo.InvokeDemo.target(InvokeDemo.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.example.demo.invoke_demo.InvokeDemo.main(InvokeDemo.java:20)
java.lang.Exception: #16
at com.example.demo.invoke_demo.InvokeDemo.target(InvokeDemo.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.example.demo.invoke_demo.InvokeDemo.main(InvokeDemo.java:20)
java.lang.Exception: #17
at com.example.demo.invoke_demo.InvokeDemo.target(InvokeDemo.java:9)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.example.demo.invoke_demo.InvokeDemo.main(InvokeDemo.java:20)

从#15到#16异常链路看,反射的调用就开始从本地实现向动态实现的转变。这 是JVM对反射调用进行辨别优化性能的一个手段。

另外注意一点,粉红色部分的字体,标记为“unkown source" ,那就是不开源的吧,所以看不到那是啥。。

四、Java反射的性能开销

public class InvokeDemo {
private static long n = 0;
public static void target(int i){
n++;
}
/* 8662ms
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<?> invokeDemo1 = Class.forName("com.example.demo.invoke_demo.InvokeDemo");
Method method1 = invokeDemo1.getMethod("target", int.class); long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
if(i==1000000000-1){
long total = System.currentTimeMillis()-start;
System.out.println(total);
}
method1.invoke(null,1);
}
}
*/
// 161ms
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
if(i==1000000000-1){
long total = System.currentTimeMillis()-start;
System.out.println(total);
}
target(1);
}
}
}

上面展示了使用反射调用和不使用反射调用的性能,结果表示,使用反射的耗时为8662ms,而不使用反射的耗时为161ms。这里就可以看到差异。

那么从字节码层面查看,又是什么样的一种风景呢?

1.不使用反射:

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=6, args_size=1
0: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: iconst_0
5: istore_3
6: iload_3
7: ldc #4 // int 1000000000
9: if_icmpge 43
12: iload_3
13: ldc #5 // int 999999999
15: if_icmpne 33
18: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
21: lload_1
22: lsub
23: lstore 4
25: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
28: lload 4
30: invokevirtual #7 // Method java/io/PrintStream.println:(J)V
33: iconst_1
34: invokestatic #8 // Method target:(I)V
37: iinc 3, 1
40: goto 6
43: return
LineNumberTable:
line 8: 0
line 9: 4
line 10: 12
line 11: 18
line 12: 25
line 14: 33
line 9: 37
line 16: 43
StackMapTable: number_of_entries = 3
frame_type = 253 /* append */
offset_delta = 6
locals = [ long, int ]
frame_type = 26 /* same */
frame_type = 250 /* chop */
offset_delta = 9

2.使用反射:

 public static void main(java.lang.String[]) throws java.lang.ClassNotFoundException, java.lang.NoSuchMethodException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=8, args_size=1
0: ldc #3 // String InvokeDemo2
2: invokestatic #4 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
5: astore_1
6: aload_1
7: ldc #5 // String target
9: iconst_1
10: anewarray #6 // class java/lang/Class
13: dup
14: iconst_0
15: getstatic #7 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
18: aastore
19: invokevirtual #8 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
22: astore_2
23: invokestatic #9 // Method java/lang/System.currentTimeMillis:()J
26: lstore_3
27: iconst_0
28: istore 5
30: iload 5
32: ldc #10 // int 1000000000
34: if_icmpge 82
37: iload 5
39: ldc #11 // int 999999999
41: if_icmpne 59
44: invokestatic #9 // Method java/lang/System.currentTimeMillis:()J
47: lload_3
48: lsub
49: lstore 6
51: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
54: lload 6
56: invokevirtual #13 // Method java/io/PrintStream.println:(J)V
59: aload_2
60: aconst_null
61: iconst_1
62: anewarray #14 // class java/lang/Object
65: dup
66: iconst_0
67: iconst_1
68: invokestatic #15 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
71: aastore
72: invokevirtual #16 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
75: pop
76: iinc 5, 1
79: goto 30
82: return

anewarray: 表示创建一个引用类型的(如类、接口、数组)数组,并将其引用值压如栈顶 (1: anewarray #2)

大致的分析:

1.绿色部分:反射调用分配了更多的栈,说明需要进行比普通调用还要多的栈空间分配,也就是pop出,push进。。

2.从方法体上看: 在反射部分代码中的蓝色背景部分,也就是62行字节码,使用了创建数组这一操作,并且还有68行的将int类型的1进行装箱操作,这些步骤对于普通调用来说,都是多出来的,自然也就比普通调用的方式耗时得多了。

但是,普通调用和反射调用一个方法的用途不一样,我们不能为了反射调用而调用,最好能够在普通调用无法满足的情况下进行该操作。

//五、优化反射调用

《深入理解Java虚拟机》- JVM是如何实现反射的的更多相关文章

  1. 深入理解JAVA虚拟机JVM

    深入理解JAVA虚拟机JVM Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制.java之所以能实现一次编写到处执行,也就是因为jVM.原理:编 ...

  2. 深入理解java虚拟机JVM(下)

    深入理解java虚拟机JVM(下) 链接:https://pan.baidu.com/s/1c6pZjLeMQqc9t-OXvUM66w 提取码:uwak 复制这段内容后打开百度网盘手机App,操作更 ...

  3. 深入理解java虚拟机JVM(上)

    深入理解java虚拟机JVM(上) 链接:https://pan.baidu.com/s/1c6pZjLeMQqc9t-OXvUM66w 提取码:uwak 复制这段内容后打开百度网盘手机App,操作更 ...

  4. 什么是HotSpot VM & 深入理解Java虚拟机 JVM

    参考 http://book.2cto.com/201306/25434.html 另外,这篇文章也是从一个系列中得出的: <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> ...

  5. 深入理解Java虚拟机-JVM运行时数据区域

    一.运行时数据区域 1.程序计数器 程序计数器( Program Counter Register) 是一块较小的内存空间, 它可以看作是当前线程所执行的字节码的行号指示器. Java虚拟机的多线程是 ...

  6. 深入理解Java虚拟机—JVM内存结构

    1.概述 jvm内存分为线程共享区和线程独占区,线程独占区主要包括虚拟机栈.本地方法栈.程序计数器:线程共享区包括堆和方法区 2.线程独占区 虚拟机栈 虚拟机栈描述的是java方法执行的动态内存模型, ...

  7. 《深入理解Java虚拟机:JVM高级特性与最佳实践》【PDF】下载

    <深入理解Java虚拟机:JVM高级特性与最佳实践>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062566 内容简介 作为一位 ...

  8. JVM | 第1部分:自动内存管理与性能调优《深入理解 Java 虚拟机》

    目录 前言 1. 自动内存管理 1.1 JVM运行时数据区 1.2 Java 内存结构 1.3 HotSpot 虚拟机创建对象 1.4 HotSpot 虚拟机的对象内存布局 1.5 访问对象 2. 垃 ...

  9. 《深入理解 java虚拟机》学习笔记

    java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.

  10. (1) 深入理解Java虚拟机到底是什么?

    好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机   作为一个Java程序员,我们每天都在写Java ...

随机推荐

  1. C# .net Ueditor实现图片上传到阿里云OSS 对象存储

    在学习的时候,项目中需要实现在Ueditor编辑器中将图片上传到云储存中,老师演示的是上传到又拍云存储,既然看了一遍,直接照搬不算本事,咱们可以依葫芦画瓢自己来动手玩玩其它的云存储服务. 现在云计算产 ...

  2. 洛谷P4994 终于结束的起点 题解

    求赞,求回复,求关注~ 题目:https://www.luogu.org/problemnew/show/P4994 这道题和斐波那契数列的本质没有什么区别... 分析: 这道题应该就是一个斐波那契数 ...

  3. 小埋的Dancing Line之旅:比赛题解&热身题题解

    答疑帖: 赞助团队:UMR IT Team和洛谷大佬栖息地 赛后题解:更新了那两道练手题的题解 赛时公告,不过一些通知也可能在团队宣言里发出 如果各位发现重题,请将你认为重复的题目链接连同这次比赛的题 ...

  4. bzoj2431 || 洛谷P1521 求逆序对

    考虑一下插⼊法 n<=100n<=100n<=100 f[i][j]f[i][j]f[i][j]表⽰111~iii的全排列有j个逆序对的⽅案数 f[i][j]=Σf[i−1][j−k ...

  5. 个人永久性免费-Excel催化剂功能第36波-新增序列函数用于生成规律性的循环重复或间隔序列

    啃过Excel函数的表哥表姐们,一定对函数的嵌套.数组公式等高级的应用有很深的体会,威力是大,但也烧死不少脑细胞,不少人就在这样的绕函数中光荣地牺牲了,走向从入门到放弃.Excel催化剂的创立,初衷就 ...

  6. [剑指offer] 46. 孩子们的游戏(圆圈中最后剩下的数)

    题目描述 随机指定一个数m,让编号为0的小朋友开始报数.每次喊到m-1的那个小朋友要出列,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友 ...

  7. eclipse(javaee windows)

    百度云:链接:http://pan.baidu.com/s/1eSoO4s6   密码:54am 官方下载网址:http://www.eclipse.org/downloads/eclipse-pac ...

  8. 详述Spring对数据校验支持的核心API:SmartValidator

    每篇一句 要致富,先修路.要使用,先...基础是需要垒砌的,做技术切勿空中楼阁 相关阅读 [小家Java]深入了解数据校验:Java Bean Validation 2.0(JSR303.JSR349 ...

  9. C++ 八数码问题宽搜

    C++ 八数码问题宽搜 题目描述 样例输入 (none) 样例输出 H--F--A AC代码 #include <iostream> #include <stdio.h> #i ...

  10. [NLP] 相对位置编码(一) Relative Position Representatitons (RPR) - Transformer

    对于Transformer模型的positional encoding,最初在Attention is all you need的文章中提出的是进行绝对位置编码,之后Shaw在2018年的文章中提出了 ...