本文由 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. ​Spring:IOC(2)

    接前文:Spring:IOC 目录 依赖注入之setter注入 依赖注入之构造器注入 特殊值处理 字面量赋值 null值 xml实体 CDATA节 为类类型属性赋值 为数组类型属性赋值 修改Stude ...

  2. KernelWarehouse:英特尔开源轻量级涨点神器,动态卷积核突破100+ | ICML 2024

    动态卷积学习n个静态卷积核的线性混合,加权使用它们输入相关的注意力,表现出比普通卷积更优越的性能.然而,它将卷积参数的数量增加了n倍,因此并不是参数高效的.这导致不能探索n>100的设置(比典型 ...

  3. LeetCode题集-3 - 无重复字符的最长子串

    题目:给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度. 我们先来好好理解题目,示例1中怎么得到长度为3的? 如果以第一个字符a为起始,不含重复的最长子串是abc:则我们这样表示(a) ...

  4. JavaScript – Set and Map

    参考 Set 和 Map 数据结构 Set 介绍和使用 Set 很像 Array, 但其实它是一个 Iteralbe 对象. 用于保存多个值, 而且具有 unique 特性 (1 个 set 里面不会 ...

  5. Dubbo框架的1个核心设计点

    Java领域要说让我最服气的RPC框架当属Dubbo,原因有许多,但是最吸引我的还是它把远程调用这个事情设计得很有艺术. 1.Dubbo优点较多,我只钟情其一 1.1.优点 业内对于微服务之间调用的框 ...

  6. neo4j 统计多个节点之间关系总和的写法-包含多个collect合并成一个并去重操作

    在使用neo4j数据库时,会遇到计算与指定节点产生关联的数量统计需求,例如指定6个节点1,2,3,4,5,6需要找出与这6个节点中4个节点有关联的节点(要求排除这6个节点的数据) 先看实现查询语句: ...

  7. 开发者故事:基于 KubeSphere LuBan 架构打造下一代云交付平台

    前言 在 KubeSphere Marketplace,个人开发者的创意和才能正在逐渐崭露头角.今日,我们荣幸地向大家介绍 Shipper 云交付平台的开发者--凌波,一位云原生领域的资深专家. 凌波 ...

  8. 云原生周刊:Kubernetes 1.29 中的删除、弃用和主要更改 | 2023.11.27

    开源项目推荐 Orphaned ConfigMaps 该版本库包含一个脚本,用于识别 Kubernetes 命名空间中的孤立的配置映射.孤立的配置映射是指那些未被命名空间中的任何活动 Pod 或容器引 ...

  9. KubeSphere 社区双周报 | KubeKey 支持 Web UI | 2023.10.13-10.26

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  10. KubeSphere 部署 Zookeeper 实战教程

    前言 知识点 定级:入门级 如何利用 AI 助手辅助运维工作 单节点 Zookeeper 安装部署 集群模式 Zookeeper 安装部署 开源应用选型思想 实战服务器配置(架构 1:1 复刻小规模生 ...