本文由 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. 2024DASCTF

    DASCTF prese 一眼控制了平坦化,可以用d810梭一下 跟进一下main_crypto这个函数 主要是两部分,第一部分是生成一个256大小的数组,通过输入的长度和遍历生成的一个数组 第二部分 ...

  2. Heart Rate Variability - HRV

    一次心跳波形 心率变异性 通常希望HRV 越高越好 HRV 公式: 需要指出的是,心率变异性会有多种计算公式. HRV数值相对越小=当天压力越大/身体越疲劳:HRV数值相对越大=当天压力越小/身体状态 ...

  3. docker image 变小的办法

    https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/ https://medium.com/sciforce/st ...

  4. 2019牛客暑期多校训练营(第四场)J-free(分层图最短路)

    >传送门< 题意:给你n个城市,m条道路,经过每一条要花费这条路的代价,现给你k个机会,使得最多k条路的代价为0,问从起点s到终点t花费的最少代价 思路:分层图最短路经典裸题 方法一 Co ...

  5. QT框架实现自定义形状截图效果

    文章目录 QT框架普通截图运行效果 QT框架系统级热键的原理 注册热键 反注册热键 获取系统级唯一的整数ID 删除系统级唯一整数ID 原生事件过滤器 QT框架截图的原理 截图窗口 拉框操作 系统剪切板 ...

  6. OxyPlot公共属性一览

    一.PlotModel 1.构造函数中设置的属性 public PlotModel() { this.Axes = new ElementCollection(this); //坐标轴集合; this ...

  7. 高强度学习训练第七天总结:JVM分配内存机制

    理解JVM内存分配策略 三大原则+担保机制 JVM分配内存机制有三大原则和担保机制 具体如下所示: 优先分配到eden区 大对象,直接进入到老年代 长期存活的对象分配到老年代 空间分配担保 对象优先在 ...

  8. /proc/zoneinfo

    root@pita2_mr813_tina35:/# cat /proc/zoneinfo Node 0, zone DMA per-node stats nr_inactive_anon 4749 ...

  9. ChallengeMobile

    解题思路 获取到输入的字符串保存到s,调用Jformat方法对s进行验证,返回true则代表输入字符串正确反之错误. Jformat方法分析: 首先看到使用了LoadData加载了"ming ...

  10. vue 中 slot 的使用方式,以及作用域插槽的用法

    分类:插槽又分为匿名插槽.具名插槽以及作用域插槽 : 匿名插槽,我们又可以叫它单个插槽或者默认插槽 因为组件标签中间是不允许写内容的,但是可以插入 插槽 :template 标签 : 插槽的使用方法 ...