《深入理解Java虚拟机》- JVM是如何实现反射的

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

时间: 2024-10-04 01:57:26

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

《深入理解Java虚拟机 JVM高级特性...》核心笔记

深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 核心笔记 JAVA 环境: JAVA虚拟机高级特性: 一:java内存区域与内存异常 一):运行数据区     1:程序计数器(Program Counter Register),也称"PC寄存器" A:用来指示需要执行哪条指令的.(在汇编语言中,CPU在得到指令之后,程序计数器便自动加1或者根据                    转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.) B:由于在JVM中,多线程

挑战高薪必看:《深入理解java虚拟机 jvm高级特性与最佳实践》

Java是目前用户最多.使用范围最广的软件开发技术之一.Java 的技术体系主要由支撑 Java程序运行的虚拟机.提供各开发领域接口支持的Java API.Java 编程语言及许多第三方 Java框架(如Spring.Struts 等)构成.在国内,有关Java API.Java语言语法及第三方框 架的技术资料和书籍非常丰富,相比之下,有关Java虚拟机的资料却显得异常贫乏. 资料获取方法 内容特色 第一部分走近 Java 本书的第部分为后文的讲解建立了良好的基础.尽管了解Java技术的来龙去脉

《深入理解Java虚拟机:JVM高级特性与最佳实践》学习笔记 Ⅲ 虚拟机执行子系统

第三部分 虚拟机执行子系统第6章 类文件结构//实现语言无关性的基础仍然是虚拟机和字节码存储格式,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其他语言的编译器一样可以把程序代码编译成Class文件,虚拟机并不关心Class的来源是什么语言,只要它符合Class文件应有的结构就可以在Java虚拟机中运行.1.Class类文件的结构Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有分隔符.Cl

《深入理解java虚拟机JVM的高级特性及其实现原理》

JVM的优点(JAVA语言的优点) 除了要了解java语言的优点之外,还要知道java语言的这些优点到底是如何实现的(也即java语言这些特性的实现原理)

深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)PDF下载

网盘下载地址:深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)PDF下载 – 易分享电子书PDF资源网 作者: 周志明 出版社: 机械工业出版社 副标题: JVM高级特性与最佳实践 出版年: 2013-9-1 页数: 433 定价: 79.00元 装帧: 平装 内容简介 · · · · · · <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)>内容简介:第1版两年内印刷近10次,4家网上书店的评论近4?000条,98%以上的评论全部为5星级的好评,是整个Java图书领域公

深入理解Java虚拟机到底是什么

摘自:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 我们都知道Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到的比较靠谱的解释: 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的

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

好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的.但是如果要问什么是虚拟机,恐怕很多人就会模棱两可了.在本文中,我会写下我对虚拟机的理解.因为能力所限,可能有些地方描述的不够欠当.如果你有不同的理解,欢迎交流. 我们都知道Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到

深入理解Java虚拟机-常用vm参数分析

Java虚拟机深入理解系列全部文章更新中... 深入理解Java虚拟机-Java内存区域透彻分析 深入理解Java虚拟机-常用vm参数分析 深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲 深入理解Java虚拟机-如何利用JDK自带的命令行工具监控上百万的高并发的虚拟机性能 深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析 深入理解Java虚拟机-你了解GC算法原理吗 话不多说,今天就分析一下一些常用的Java虚拟机的参数设置,以及如何更好

深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析

Java虚拟机深入理解系列全部文章更新中... 深入理解Java虚拟机-Java内存区域透彻分析 深入理解Java虚拟机-常用vm参数分析 深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲 深入理解Java虚拟机-如何利用JDK自带的命令行工具监控上百万的高并发的虚拟机性能 深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析 深入理解Java虚拟机-你了解GC算法原理吗 前面在学习JVM的知识的时候,一般都需要利用相关参数进行分析,而分析一般

深入理解java虚拟机第二版(四)虚拟机性能监控与故障处理工具

JDK的命令行工具(jps:虚拟机进程状况工具,jstat:虚拟机统计信息监视工具,jinfo:Java配置信息工具,jmap:Java内存映像工具,jhat:虚拟机堆转储快照分析工具,jstack:Java堆栈跟踪工具):可视化工具(JConsole,VisualVM) 一. JDK的命令行工具 jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程 jstat:JVM Statistics Monitoring Tool,用户收集HotSpot虚