在运行期通过反射了解JVM内部机制
本文由 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内部机制的更多相关文章
- 并发—JVM内部机制和外部机制处理方法
并发常见的编程场景,一句话概括就是,需要协调多个线程之间的协作,已保证程序按照自己原本的意愿执行.那么究竟应该如何协调多个线程? 这个问题比较宽泛,一般情况下,我们按照方式的纬度去简单区分,有以下两种 ...
- JVM总结(六):晚期(运行期)优化
这节我们总结一下JVM运行期的优化问题. JVM运行期优化 即时编译器(JIT) 编译对象与触发条件 编译对象 触发条件 编译过程 编译优化技术 JVM运行期优化 Java程序在运行的期间,可能会有某 ...
- JVM原理:4 运行期优化
JVM运行期优化 Java程序在运行的期间,可能会有某个方法或者代码块的运行特别频繁时,就会把这些代码认定为“热点代码”.为了提高热点代码的执行效率,在运行时JVM会将这些代码编译成与本地平台相关的机 ...
- jvm虚拟机笔记<六> 运行期优化
这节我们总结一下JVM运行期的优化问题. https://www.cnblogs.com/zhouyuqin/p/5224573.html JVM运行期优化 即时编译器(JIT) 编译对象与触发条件 ...
- JVM(1)---虚拟机在运行期的优化策略
1.解释器与JIT编译器 首先我们先来了解一下运行在虚拟机之上的解释器与JIT编译器.当我们的虚拟机在运行一个java程序的时候,它可以采用两种方式来运行这个java程序: 采用解释器的形式,也就是说 ...
- 深入了解JVM虚拟机8:Java的编译期优化与运行期优化
java编译期优化 java语言的编译期其实是一段不确定的操作过程,因为它可以分为三类编译过程:1.前端编译:把.java文件转变为.class文件2.后端编译:把字节码转变为机器码3.静态提前编译: ...
- JVM类加载机制与反射-转
一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允 ...
- JVM-程序编译与代码晚期(运行期)优化
晚期(运行期)优化 1.为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time,JI ...
- JIT晚期(运行期)
在部分的商用虚拟机(Sun HotSpot.IBM J9)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为& ...
- 晚期(运行期)优化---HotSpot虚拟机内的即时编译器
最初java程序是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”.为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相 ...
随机推荐
- Windows平台体验StableSwarmUI-0.6.4-Beta经验版
目录 StableSwarmUI install 经验版 StableSwarmUI 配置后端 StableSwarmUI 快捷安装脚本 StableSwarmUI 安装与启动 sd_xl_base_ ...
- 8.18域横向smb&wmi明文或hash传递
知识点 windows 2012以上版本默认关闭wdigust,攻击者无法从内存中获取明文密码: Windows2012以下版本如安装KB287199补丁,同样也无法从内存中获取明文密码: 解决方法: ...
- CPP在内网穿透技术的思考
概述 内网穿透是一种技术,用于在私有局域网(LAN)中的设备与外部网络(如互联网)之间建立通信通道,使得外部设备可以访问内网中的服务.由于内网设备通常位于防火墙或 NAT(网络地址转换)设备之后,外部 ...
- ASP.NET Core – ADO.NET
前言 自从用 Entity Framework 就再也没有用过 ADO.NET 了. 很多年前写过 基础 ADO.NET 访问MYSQL 与 MSSQL 数据库例子. 今天刚好想做个单侧, 那就顺便翻 ...
- CSS – Icon
前言 Icon 并不容易搞. 市场有许多平台支持 Icon, 有些收费有些免费. 有些 icon 很丰富, 有些很缺失. 尤其是在做网站的时候寻找 icon 是一个挺累的事情. 这篇就来聊聊 icon ...
- 嵌入式Linux ubi文件系统制作、分区设置、只读文件系统,uboot启动参数root
当前平台, 基于君正的X10000平台的嵌入式Linux 系统 0 目的 我要设置根文件系统为可读写, 设置data分区上的文件系统为只读 1 设置各文件系统的读写属性 /bin/mount -o ...
- Docker数据共享与持久化(六)
接下来介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式: 数据卷(Data Volumes) 挂载主机目录 (Bind mounts) 一.数据卷 数据卷是一个可供一 ...
- template<> 模板特化
template<> 是用于 模板特化(Template Specialization) 的一种语法. 模板特化允许你为某些特定的模板参数提供不同的实现.例如,template<&g ...
- 1.2 HELLO 三角形
这一节,我觉得是相当有难度的.渲染一个三角形,就需要介绍GLSL语言,图形渲染管线(Graphics Pipeline)以及着色器(Shader),标准化设备坐标(NDC)等诸多概念. 图形渲染管线和 ...
- 关于自定义事件父子组件传值问题 $event
1.$event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event 1.1在原生事件中,$event是事件对象 可以点出来属性 2.在原生事件中,$event是事件对象,在自定义事件 ...