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 进行装箱操作, 这些步骤对于普通调用来说, 都是多出来的, 自然也就比普通调用的方式耗时得多了.
但是, 普通调用和反射调用一个方法的用途不一样, 我们不能为了反射调用而调用, 最好能够在普通调用无法满足的情况下进行该操作.
五, 优化反射调用
(明天再写吧...demo 都没写出来, 不好意思写了..)
来源: https://www.cnblogs.com/chenscript/p/11397328.html