深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
28017 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

07 | JVM是如何实现反射的?

郑雨迪 2018-08-06
今天我们来聊聊 Java 里的反射机制。
反射是 Java 语言中一个相当重要的特性,它允许正在运行的 Java 程序观测,甚至是修改程序的动态行为。
举例来说,我们可以通过 Class 对象枚举该类中的所有方法,我们还可以通过 Method.setAccessible(位于 java.lang.reflect 包,该方法继承自 AccessibleObject)绕过 Java 语言的访问权限,在私有方法所在类之外的地方调用该方法。
反射在 Java 中的应用十分广泛。开发人员日常接触到的 Java 集成开发环境(IDE)便运用了这一功能:每当我们敲入点号时,IDE 便会根据点号前的内容,动态展示可以访问的字段或者方法。
另一个日常应用则是 Java 调试器,它能够在调试过程中枚举某一对象所有字段的值。
(图中 eclipse 的自动提示使用了反射)
在 Web 开发中,我们经常能够接触到各种可配置的通用框架。为了保证框架的可扩展性,它们往往借助 Java 的反射机制,根据配置文件来加载不同的类。举例来说,Spring 框架的依赖反转(IoC),便是依赖于反射机制。
然而,我相信不少开发人员都嫌弃反射机制比较慢。甚至是甲骨文关于反射的教学网页 [1],也强调了反射性能开销大的缺点。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(55)

  • 志远
    老师您好,提个建议,您讲课过程中经常提到一些概念名词,您讲课总是预设了一个前提,就是假设我们已经知道那个概念,然而并不清楚。比如本文中被不断提到的内联,什么是内联呢?

    作者回复: 谢谢建议!方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用这么个过程。

    2018-08-06
    44
  • xiaguangme
    开发人员日常接触到的 Java 集成开发环境(IDE)便运用了这一功能:每当我们敲入点号时,IDE 便会根据点号前的内容,动态展示可以访问的字段或者方法。//这个应该是不完全正确的,大部分应该是靠语法树来实现的。

    作者回复: 谢谢指出!

    2018-08-06
    26
  • godtrue
    小结
    1:反射机制是Java语言的一个非常重要的特性,通过这个特性,我们能够动态的监控、调用、修改类的行为,许多的框架实现就用到了Java语言反射的机制

    2:使用反射挺好的,但它也是不完美的,复杂的操作往往更耗时间和精力,使用反射也是一样,性能低下是她所被人诟病的一个地方,那为什么方法的反射如此耗费性能呐?它的性能耗在那里呢?方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联。
    2018-08-07
    12
  • 李杰
    当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码...
    ———可以认为第16次反射调用时的耗时是最长的吗?

    作者回复: 动态生成发生在第15次(从0开始数的话),所以第15次比较耗时。

    2018-08-06
    9
  • ext4
    雨迪您好,我有两个问题:

    一是我自己的测试结果和文章中有些出入。在我自己的mac+jdk10环境中,v3版本的代码和v2版本性能是差不多的,多次测试看v3还略好一些。从v2的GC log来看for循环的每一亿次iteration中间都会有GC发生,似乎说明这里的escape analysis并没有做到allocation on stack。您能想到这是什么原因么?另有个小建议就是文章中提到测试结果时,注明一下您的环境。

    另一个问题是在您v5版本的代码中,您故意用method1和method2两个对象霸占了2个ProfileType的位子,导致被测的反射操作性能很低。这是因为此处invoke方法的inline是压根儿就没有做呢?还是因为inline是依据target1或者target2来做的,而实际运行时发现类型不一致又触发了deoptimization呢?

    望解答,谢谢~

    作者回复: 多谢建议,我也是mac+jdk10。我这边裸跑v2是2.7x(因为每次要新建整数对象所以有GC),加大整数缓存后跑v2是1.8x(无GC)。你是否忘了加大整数缓存?

    第二个问题,研究得很深!Method.invoke一直会被内联,但是它里面的MethodAccesor.invoke则不一定。

    实际上,在C2编译之前循环代码已经运行过非常多次,也就是说MethodAccesor.invoke已经看到多次调用至target()的动态实现。在profile里会显示为有target1,有target2,但是profile不完整,即还有一大部分的调用者类型没有记录。

    这时候C2会选择不inline这个MethodAccesor.invoke调用,直接做虚调用。

    2018-08-06
    8
  • 小明
    可以看看这篇博客 有详细的生成动态调用的解释
    http://rednaxelafx.iteye.com/blog/548536
    2018-08-17
    7
  • Kisho
    郑老师,你好,
           “动态实现无需经过Java到C++再到Java的切换”,这句话没太明白,能在解释下么?

    作者回复: 在v0版本中我贴了一段stacktrace,你可以看到中间有个native method,这就是C++代码,也就是它先调用至这个C++代码,在C++代码里面再调用至Java代码。

    2018-08-06
    5
  • Stephen
    老师,有三个知识点不太明白:内联、逃逸分析以及inflation机制
    2018-11-03
    1
    4
  • Yoph
    MethodAccessor实例创建在ReflectionFactory中,如下代码:
    public class ReflectionFactory {
        private static boolean noInflation = false;
        private static int inflationThreshold = 15;
       
        public MethodAccessor newMethodAccessor(Method method) {
            checkInitted();
            if (noInflation) {
                return new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            } else {
                NativeMethodAccessorImpl acc =
                    new NativeMethodAccessorImpl(method);
                DelegatingMethodAccessorImpl res =
                    new DelegatingMethodAccessorImpl(acc);
                acc.setParent(res);
                return res;
            }
        }
    }

    实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。
    2018-11-13
    3
  • godtrue
    老师请教个问题,如果手动修改某个Java字节码文件,如果JVM不重新加载此文件,有什么方式能让JVM识别并执行修改的内容呢?
    如果一定需要JVM加载后才能识别并执行,有什么好的手动触发的方法呢?

    作者回复: 你可以搜一下class redefinition的相关资料。我以前用cagent做过,Javaagent应该也可以。

    2018-08-06
    3
  • 小鳄鱼
    不是同个对象,但equal。老师说了,返回的是目标方法的一份拷贝
    2018-11-12
    2
  • life is short, enjoy mor...
    总结一下
    反射有两种实现方式:

    本地方法调用(就是字节码中已经定义好的方法)

    动态生成字节码


    两者有什么区别?

    动态生成字节码(以下简称动态实现),生成字节码的过程很慢(类似于准备工作),但是执行效率高。

    本地方法调用,不用生成字节码,直接调用本地方法。所以准备工作几乎没有,很快。但是执行效率就差很多。


    JVM如何做决定选择哪种实现方式?

    通过反射执行的次数来决定,默认值是15。15次之前直接本地调用,之后动态实现。


    JVM为啥分两种实现方式?

    本地实现的调用流程复杂。而在执行多次的情况下,复杂意味着性能损耗,所以有一种适合多次执行的解决方案,就是动态生成字节码。

    2018-10-06
    2
  • once
    请问老师 是不是本地方法的性能一般都不是很好呢

    作者回复: 需要经过JNI,所以性能很不好。

    不过即时编译器可能会将某些指定的本地方法调用给替换掉。这些特定的本地方法叫intrinsics,下周一会讲。

    2018-09-07
    2
  • Scott
    有两个问题:
    1.v3版本中,确定不逃逸的数组可以优化访问,这个是怎么做的?
    2.v5版本中,为啥逃逸分析会失效,明明都封闭在循环里的?

    作者回复: 1. 读数组被替换为之前写入数组的值。后面数组就只有写没有读了,因此可以优化掉。
    2. 只要没有完全内联,就会将看似不逃逸的对象通过参数传递出去。即时编译器不知道所调用的方法对该对象有没有副作用,所以会将其判定为逃逸。
    (如果你问的是不能分配到栈上,那我只能回答Java虚拟机从设计上不支持栈分配。它要不是堆分配,要不是虚拟分配+标量替换)

    2018-08-15
    2
  • godtrue
    JVM加载了一个Java字节码文件,在不停止JVM的情况下能再次的加载同一个Java字节码文件吗?如果能是覆盖了原来的那个Java字节码文件还是怎么着了呢?
    在IDE中是可以直接修改Java源代码的,然后可以手动触发Java源代码的编译和重新加载,请问老师知道IDE是怎么实现的吗?
    2018-08-06
    1
    2
  • 尔东
    两个method的对象是一样的,对结果没有影响
    2019-01-01
    1
  • Stephen
    老师,有三个知识点不太明白,分别是:内联、逃逸分析以及inflation机制

    作者回复: 内联和逃逸分析后面有两篇会专门介绍,反射的inflation机制是当反射被频繁调用时,动态生成一个类来做直接调用的机制,可以加速反射调用

    2018-11-03
    1
  • 星文友
    给大家讲个笑话:
    我负责的项目中有大量动态生成的类,这些类实例的调用原本都是通过反射去完成,后来我觉得反射效率低,就为每个动态类的每个方法在动态生成一个代理类,这个代理类就是进行类型强转然后直接调用。后来在压测环境进行测试,发现并无卵用,早点开到这篇文章我就不用做这么多无用功了。特么的JVM已经有这个功能了啊

    作者回复: 这说明你和JVM架构师想一块去了 ;)

    2018-11-02
    1
  • vick
    请教一个问题,本地实现可以用java来替代c++的实现方式吗?这样就可以避过C++的额外开销?

    作者回复: 之所以叫本地实现,就是因为它用的C++代码。如果用Java来实现,就不会这么叫啦 :)

    JVM有用Java来替代的实现方式,也就是文中介绍的动态实现。它是根据反射调用的目标方法来动态生成字节码的。

    2018-09-08
    1
  • 沉淀的梦想
    在v5版本中,为什么我设置TypeProfileWidth为3,速度并没有变快呢?

    作者回复: 可以再增大到8试试。

    2018-08-18
    1
收起评论
55
返回
顶部