深入拆解 Java 虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
87446 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
模块四:黑科技 (3讲)
深入拆解 Java 虚拟机
15
15
1.0x
00:00/00:00
登录|注册

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

其他反射API用法
使用反射功能的常用操作
获取Class对象的方法
误扰Method.invoke方法的类型profile
提高Java虚拟机关于每个调用能够记录的类型数目
在循环外构造参数数组并直接传递给反射调用
扩大Integer缓存范围或缓存自动装箱得到的Integer对象
方法内联
基本类型的自动装箱、拆箱
变长参数方法导致的Object数组
Inflation机制在调用次数达到阈值时切换委派对象至动态实现
动态实现通过生成字节码调用目标方法
本地实现通过Java虚拟机内部调用目标方法
第一次反射调用生成委派实现,委派本地实现
MethodAccessor有本地实现和委派实现
反射API简介
优化方案
性能开销主要来源
Method.invoke委派给MethodAccessor
总结与实践
反射调用的开销
反射调用的实现
Java反射机制

该思维导图由 AI 生成,仅供参考

今天我们来聊聊 Java 里的反射机制。
反射是 Java 语言中一个相当重要的特性,它允许正在运行的 Java 程序观测,甚至是修改程序的动态行为。
举例来说,我们可以通过 Class 对象枚举该类中的所有方法,我们还可以通过 Method.setAccessible(位于 java.lang.reflect 包,该方法继承自 AccessibleObject)绕过 Java 语言的访问权限,在私有方法所在类之外的地方调用该方法。
反射在 Java 中的应用十分广泛。开发人员日常接触到的 Java 集成开发环境(IDE)便运用了这一功能:每当我们敲入点号时,IDE 便会根据点号前的内容,动态展示可以访问的字段或者方法。
另一个日常应用则是 Java 调试器,它能够在调试过程中枚举某一对象所有字段的值。
(图中 eclipse 的自动提示使用了反射)
在 Web 开发中,我们经常能够接触到各种可配置的通用框架。为了保证框架的可扩展性,它们往往借助 Java 的反射机制,根据配置文件来加载不同的类。举例来说,Spring 框架的依赖反转(IoC),便是依赖于反射机制。
然而,我相信不少开发人员都嫌弃反射机制比较慢。甚至是甲骨文关于反射的教学网页[1],也强调了反射性能开销大的缺点。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了Java反射机制的实现原理及性能问题。通过分析Method.invoke的源代码,揭示了反射调用的实现机制,包括本地实现、委派实现和动态实现。文章指出动态实现相比本地实现运行效率更高,但生成字节码耗时较长。此外,文章还介绍了反射调用的Inflation机制,即在反射调用次数达到一定阈值时,会切换至动态实现。通过实例代码展示了Inflation机制的触发过程。最后,文章提到可以通过参数关闭Inflation机制。 文章通过实例代码和性能测试,展示了反射调用的性能开销,并提出了优化方案。作者通过多个版本的代码演示,分析了反射调用中的性能瓶颈,包括自动装箱、Object数组生成和内联优化等问题。最终,通过关闭Inflation机制和权限检查,以及优化类型profile的方式,将反射调用的性能开销降低到原本的1.3倍。 总的来说,本文对Java反射机制进行了深入剖析,通过实例代码和性能测试展示了反射调用的性能优化方案。对于想深入了解Java反射机制的读者来说,提供了深入的技术分析和实际案例,帮助读者更好地理解反射机制的实现原理和性能优化。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(81)

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

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

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

    作者回复: 谢谢指出!

    2018-08-06
    61
  • 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
    26
  • 夜空
    当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码... ———可以认为第16次反射调用时的耗时是最长的吗?

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

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

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

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

    作者回复: 之所以叫本地实现,就是因为它用的C++代码。如果用Java来实现,就不会这么叫啦 :) JVM有用Java来替代的实现方式,也就是文中介绍的动态实现。它是根据反射调用的目标方法来动态生成字节码的。

    2018-09-08
    3
    13
  • Kisho
    郑老师,你好, “动态实现无需经过Java到C++再到Java的切换”,这句话没太明白,能在解释下么?

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

    2018-08-06
    13
  • Stephen
    老师,有三个知识点不太明白,分别是:内联、逃逸分析以及inflation机制

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

    2018-11-03
    12
  • once
    请问老师 是不是本地方法的性能一般都不是很好呢

    作者回复: 需要经过JNI,所以性能很不好。 不过即时编译器可能会将某些指定的本地方法调用给替换掉。这些特定的本地方法叫intrinsics,下周一会讲。

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

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

    2018-08-15
    7
收起评论
显示
设置
留言
81
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部