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

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

今天我们来聊聊 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 虚拟机》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(82)

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

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

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

    作者回复: 谢谢指出!

    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调用,直接做虚调用。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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