本文由 ImportNew - 黄飞飞 翻译自 takipioncode。欢迎加入Java小组。转载请参见文章末尾的要求。

在日常工作中,我们都习惯直接使用或者通过框架使用反射。在没有反射相关硬编码知识的情况下,这是Java和Scala编程中使用的类库与我们的代码之间进行交互的一种主要手段。但是,使用反射仅限于JVM内部运行的Java和Scala代码。假使在运行期通过反射既能查看自己的代码又能看到JVM的代码,会有怎样的效果呢 ?

当我们开始创建 Takipi 的时候,试图寻找一种通过分析JVM堆内存来进行一些底层优化的有效方法,比如扫描一个托管堆块(managed
heap block)的地址空间。我们找到了许多有趣的工具和组件用来检测JVM状态的各个方面,其中一个就是在运行期通过反射了解JVM内部机制。

译注: Takipi是一家以色列创业公司,发现了一种构建后端服务器网络的新方式,可以使用非常简化的除错流程帮助企业定位错误。

Java可服务性代理(Serviceablity Agent,简写SA)是最强大和最底层的Java调试工具之一。这个强大的工具是HotSpot JDK自带的。使用它不仅可以看到堆中的Java对象,还可以看到内部
C++ 对象,包括JVM本身。那才是真正魔法开始的地方。

反射的构成:在运行时动态检测和修改对象时,无论使用何种形式的反射都不能缺少两个必要信息。第一个是想要检测的对象引用(或者地址);第二个是对象结构描述,包括所有字段的偏移量以及它们的类型信息。如果支持动态方法调用,这个结构还需要包括类方法表 (比如 vtable)的引用,以及每个方法需要的参数。

Java反射本身是非常简单的,通过反射获取目标对象的引用与获取其它方式一样。使用 Object.getClass 通用方法(最开始从类的字节码中加载)获取字段和方法结构。真正的问题是:如何反射JVM本身?

反射的关键:令人惊奇的是, JVM通过一套公开的输出符号暴露了其内部的类型系统。 这些符号给可服务性代理(或其他工具)提供了访问JVM内部类系统结构和地址的方法。通过这些符号,几乎可以检测到JVM内部在最底层运行机制的所有方面,包括诸如原始堆地址、线程/栈地址以及编译器内部状态等。

反射实战:为了增加对这种方式的了解,启动可服务性代理的HotSpot调试程序界面,可以看到正在运行的一些功能。将 sun.jvm.hotspot.HSDB 作为主类参数启动 sa-jdi.jar,可以看到与其它JVM功能强大调试工具,例如 jmap、jinfo 和 jstack等,一样的底层信息。

HSDB及其为目标JVM提供的一些非常底层的检测功能

具体实现:让我们仔细了解这些由JVM提供的功能。这种方法的基础是由 jvm 函数库公开导出的 gHotSpotVMStructs 结构。这个结构暴露了JVM内部的类型系统,也为我们提供了可以开始反射的根对象地址。可以通过 JNI 或者 JNA访问这个符号,调用方式与访问动态链接的操作系统公开系统库符号一样。

接下来问题就变成:怎样解析由 gHotSpotVMStructs 符号中地址的数据?从下表中可以看到,JVM不仅暴露了类型系统的地址以及根对象地址,还提供一些额外的符号用来解析需要的数据值。这些数据包括已定位的类描述符及其二进制偏移量。

jvm.dll暴露符号的一个dependency
walker截图

清单(manifest): gHotSpotVMStructs 结构指向了类及其字段的一个列表,每个类提供了一个字段列表。对于每个字段,该结构提供了它的名称、类型以及是否静态字段。如果是一个静态字段,这个结构还会提供目标对象的访问地址。该地址可用作反射JVM内部特定组件的根,包括诸如编译器、线程处理或者收集堆系统等。

可以从这里检出
Hotspot JDK 中可服务性代理用来解析 gHotSpotVMStructs 的实际算法。

实例:既然已经大概了解了这些功能,现在看一些由该接口访问数据类型的示例。那些编写SA代理的人,在为gHotSpotVMStructs表中的类创建Java封装时克服了很多麻烦。在访问大多数内部系统时,这些封装提供一套简洁的API,不仅类型安全而且还隐藏了一些解析数据所必须的二进制工作。

为了更好地了解这些API提供的强大功能,下面是其中的一些底层类介绍:

  • VM 是一个单例类,它暴露了JVM的许多内部系统,比如线程系统、内存管理以及集合功能。可以把它当作JVM许多子系统的一个入口。当你研究API时,可以从这里入手。
  • JavaThread 可以让你从JVM角度理解一个Java线程, 同时还有(编译后的、经过解释的和本地)帧位置和类型的深入信息以及实际的本地堆栈和CPU寄存器信息。
  • CollectedHeap 可以让你研究收集堆(collected
    heap)的原始内容。由于HotSpot包含多种GC的实现,CollectedHeap 就是供具体实现比如 ParallelScavengeHeap 必须继承的抽象类。每一个都提供包含Java对象实际地址的一组内存区域。

当你在看每个类的实现时,你会发现本质上它们就是使用类似反射API来查看JVM内存硬编码的包装器。

C++中的反射:这些Java包装器中的每一个都被设计为JVM内部一个近乎完整的C++类镜像。我们知道,C++ 没有原生的反射能力,这就让我们产生了一个疑问:如何在这之间建立的纽带?

答案就是JVM开发者所了一些非常特别的事情。经过一系列C++宏指令以及大量的艰苦工作,HotSpot 开发小组手动将许多内部C++类的字段结构映射并加载到全局的 gHotSpotVMStructs 中。这个过程就是可以从外部进行反射的原因。在JVM编译期间确定了实际字段偏移量的值和布局,帮助并确保了输出结构兼容JVM目标操作系统。

跨进程连接:可服务性代理有另外一个强大的功能值得我们关注。从进程外反射一个内部运行的JVM是SA框架提供的超酷功能之一,通过把可服务性代理附加到目标JVM作为一个操作系统级别的调试器可以实现这一功能。由于 SA 代理框架是依赖于操作系统的,对于 Linux 会利用
gdb 调试器进行连接,对于Windows 则会使用winDbg (这意味着需要Windows 调试工具)。调试器框架是可扩展的,这意味着可以通过继承 DebuggerBase 这个抽象类来使用另一个调试器。

一旦建立了调试器的连接,gHotSpotVMStruct的返回地址值会传回到调试器进程,这样可以(借助操作系统)开始检测甚至是修改目标JVM的内部对象系统。这就是HSDB如何实现连接并调试一个目标JVM,包括Java代码和JVM代码。

HSDB接口为SA代理提供了反射目标JVM进程功能

希望本文能够勾起你的兴趣。 以我个人观点来看,这个架构是我最喜欢的JVM设计之一 。它的优雅和开放绝对令人惊叹。在我们构建 Takipi 实时编码模块时超级有用, 所以要感谢它的设计者。

你是否已经在代码中使用了这些API?非常乐意在下面的评论中听到它,或者回答你们的任何问题。

在运行期通过反射了解JVM内部机制的更多相关文章

  1. 并发—JVM内部机制和外部机制处理方法

    并发常见的编程场景,一句话概括就是,需要协调多个线程之间的协作,已保证程序按照自己原本的意愿执行.那么究竟应该如何协调多个线程? 这个问题比较宽泛,一般情况下,我们按照方式的纬度去简单区分,有以下两种 ...

  2. JVM总结(六):晚期(运行期)优化

    这节我们总结一下JVM运行期的优化问题. JVM运行期优化 即时编译器(JIT) 编译对象与触发条件 编译对象 触发条件 编译过程 编译优化技术 JVM运行期优化 Java程序在运行的期间,可能会有某 ...

  3. JVM原理:4 运行期优化

    JVM运行期优化 Java程序在运行的期间,可能会有某个方法或者代码块的运行特别频繁时,就会把这些代码认定为“热点代码”.为了提高热点代码的执行效率,在运行时JVM会将这些代码编译成与本地平台相关的机 ...

  4. jvm虚拟机笔记<六> 运行期优化

    这节我们总结一下JVM运行期的优化问题. https://www.cnblogs.com/zhouyuqin/p/5224573.html JVM运行期优化 即时编译器(JIT) 编译对象与触发条件 ...

  5. JVM(1)---虚拟机在运行期的优化策略

    1.解释器与JIT编译器 首先我们先来了解一下运行在虚拟机之上的解释器与JIT编译器.当我们的虚拟机在运行一个java程序的时候,它可以采用两种方式来运行这个java程序: 采用解释器的形式,也就是说 ...

  6. 深入了解JVM虚拟机8:Java的编译期优化与运行期优化

    java编译期优化 java语言的编译期其实是一段不确定的操作过程,因为它可以分为三类编译过程:1.前端编译:把.java文件转变为.class文件2.后端编译:把字节码转变为机器码3.静态提前编译: ...

  7. JVM类加载机制与反射-转

    一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允 ...

  8. JVM-程序编译与代码晚期(运行期)优化

    晚期(运行期)优化 1.为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time,JI ...

  9. JIT晚期(运行期)

    在部分的商用虚拟机(Sun HotSpot.IBM J9)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为& ...

  10. 晚期(运行期)优化---HotSpot虚拟机内的即时编译器

    最初java程序是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”.为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相 ...

随机推荐

  1. php自定义函数访问其他地方定义的变量

    新捣鼓php,很多地方有.net的思维好难改过来. 在Connection/config.php 自定义了数据库连接字符串 然后在外部页面,自定义了一个function,对数据库进行操作 然后死活都给 ...

  2. 【YashanDB知识库】ODBC驱动类问题定位方法

    [标题]ODBC驱动类问题定位方法 [需求分类]故障分析 [关键字]ODBC [需求描述]由于我们的ODBC接口目前尚不完善,经常会遇见ODBC接口能力不足导致应用功能无法运行的问题,需要定位手段确定 ...

  3. 【YashanDB知识库】收集分区表统计信息采样率小于1导致SQL执行计划走偏

    [问题分类]性能优化,BUG [关键字]分区表,统计信息,采样率 [问题描述]收集表(分区表)级别的统计信息时,如果采样率小于1,dba_ind_statistics中partition_name i ...

  4. LCD屏幕显示PNG图像

    正点原子LCD屏幕显示PNG图像 本文概要 这段时间在学习正点原子的IMX6ULL开发板,在应用编程中有一个代码练习是需要在LCD屏幕上显示PNG图像,但由于我的屏幕参数和教程中的有些出入,于是经过自 ...

  5. Email 关于 POP3 IMAP SMTP office365 Outlook Gmail G-Suit shared mailbox小小理解

    Outlook 是微软的一个 email 软件, 管理 email 的 UI. Gmail 是 google 的 office365 是一个配套, 里面有 email, one drive, exce ...

  6. Java如何解决同时出库入库订单号自动获取问题

    在Java中处理同时出库和入库的订单号自动获取问题,通常涉及到多线程环境下的并发控制.为了确保订单号的唯一性和连续性,我们可以使用多种策略,如数据库的自增ID.分布式锁.或者利用Java的并发工具类如 ...

  7. 【赵渝强老师】Oracle数据库的存储结构

    Oracle的存储结构分为:物理存储结构和逻辑存储结构. 一.物理存储结构:指硬盘上存在的文件 数据文件(data file) 一个数据库可以由多个数据文件组成的,数据文件是真正存放数据库数据的.一个 ...

  8. Android libusb

    一.环境:配置NDK环境 1.下载libusb源码: https://github.com/libusb/libusb/releases,如下图所示 2.删除一些和Android平台无关的文件,删除后 ...

  9. Java日期时间API系列18-----Jdk8中java.time包中的新的日期时间API类,java日期计算5,其他常用日期计算,星期计算,闰年计算等

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析中可以看出,java8中的方法特别丰富,一些常用的计算如星期计算,闰年计算等 ...

  10. Oracle、MySQL等数据库故障处理优质文章分享 | 10月汇总

    墨天轮社区于9月起持续举办[聊聊故障处理那些事儿]DBA专题征文活动,每月进行评优发奖,鼓励大家记录工作中遇到的数据库故障处理过程,不仅用于自我复盘与分析,同时也能帮助其他的同仁们避坑. 上月为大家梳 ...