更多文章请移步:Java译站
我们经常会在工作中用到反射,要么直接使用,要么通过一些框架。在Java和Scala编程里,如果想要和我们的代码进行跟踪交互,却又希望对代码透明,最主流的一个方式就是反射。不过我们用到的反射通常都局限在Java和Scala代码里,并运行在JVM中。如果我们不仅是要跟踪自己的代码,还想跟踪JVM的代码怎么办呢?
当我们开始构建
Takipi站点的时候,我们想寻找一种能有效跟踪JVM堆内存的方式,以便进行一些偏底层的优化,比如扫描某个托管的内存块的地址空间。
Java Serviceability Agent是Java一个非常强大的底层调试工具。这个工具在HotSpot JDK中自带,通过它我们不仅能查看堆里的Java对象,还能跟踪到JVM内部的C++对象,这个才是它的真正价值所在。
反射的要素: 当在运行时用反射来查看或者修改对象的时候,需要有两个基本的要素。第一个是需要查看的对象的引用(或者说地址)。第二个是这个对象结构的描述信息,包括字段的偏移量和它们的类型信息。如果支持动态方法调用,这个结构还包含了一个类方法表(vtable)的引用,以及每个方法的参数信息。
Java的反射是相当直接的。获取一个目标对象的引用非常简单。它的字段和方法的结构也都能通过Object.getClass()方法获取到(从类的字节码中加载的)。不过问题是你怎么对JVM进行反射呢?
开启宝库的钥匙。幸运的是,JVM通过一些对外提供的接口将它的内部类型系统暴露了出来。通过这些接口Serviceability Agent(别的工具也是类似的)才能访问JVM内部类的结构和地址。有了它们,你就可以观察从底层观察JVM内部运行的所有细节,包括原始堆的地址空间,线程/栈的地址,内部编译器的状态。
反射的应用。你可以启动一下Serviceability Agent的Hotspot调试器来体验一下这些功能。只需要运行一下sa-jdi.jar包中的sun.jvm.hotspot.HSDB的main方法就可以了。JVM的其它一些调试工具比如 jmap,jinfo和jstack等也是基于这些基础功能来实现的。
这是怎么实现的?我们来看下JVM是如何提供这些功能的。JVM库对外提供的gHotSpotVMStructs结构是这整个的基石。它暴露了JVM内部的类型系统以及根对象的地址,有了它们就可以进行对象的反射了。就像通过JNI或者JNA来动态链接到操作系统的一些值一样,你也可以这样来对它进行访问。
问题的关键在于你如何解析这个gHotSpotVMStructs符号里面的数据。正如下表所示,JVM不仅暴露了它的内部类型系统的地址和根对象地址,还有用以解析这些数据的一些额外的符号和值。这包含类描述信息和每个字段在这个类里的偏移量。
[img]http://takipi.wpengine.netdna-cdn.com/wp-content/uploads/2014/01/dependencywalker.jpg
[/img]
描述信息。gHotSpotVMStructs结构指向了很多类以及它们的字段。每个类都有一系列的字段,每个字段又都包含它们的名字,类型,以及是否是静态的。如果是静态字段这个结构还可以用来访问它的值。对于一个静态的对象字段,这个结构体还会提供目标对象的地址。通过这个根地址我们可以开始反查JVM内部的一些组件,包括编译器,线程还有堆。
你可以下载HotSpot JDK的源码来了解下Serviceability agent的具体的算法。
开始动手吧。我们已经大概了解了这些功能到底能干什么,现在来看使用这个接口获取信息的一些具体例子。SA项目的这些人费了好多工夫来给gHotSpotVMStructs暴露出的这些类来生成Java的包装类。通过这些包装类提供出来的接口让访问JVM内部系统的工作变成非常 简单和方便,不仅保证了类型安全,同时也解决了访问和解析数据的烦恼。
为了让你能理解这个接口提供的强大的功能,下面列出了它提供的一些底层类的相关信息——
VM, 它是一个单实例对象,它暴露了JVM内部的许多子系统,比如线程子系统,内存管理和回收子系统。它是JVM各个子系统的入口,想要开始探索这个API的话,这是个不错的地方。
JavaThread,通过它你可以了解到JVM内部是如何处理线程的,详细到各个栈帧的位置和类型(编译的,解释执行,或者本地的),甚至本地栈和CPU寄存器的信息都有。
CollectedHeap,通过它你可以看到堆的原始内容。HotSpot提供了多个垃圾回收的实现,这是那些具体实现比如 ParallelScavengeHeap的抽象基类。每个堆都是一些内存块的集合,这些内存块包含Java对象的确切地址。
你如果看一下这些类的实现你会发现它们实际上就是使用类似反射的API来查看JVM的内存的一些硬编码的包装类。
C++的反射。每个包装类其实就是JVM内部的C++类的一个镜像。我们都知道C++原生是不支持反射的,那么这个桥接的工作是如何完成的呢?
这是由于JVM开发人员做了一个独特的事情。通过一系列的C++宏和许多细致的工作,HotSpot团队手动将JVM内部的C++类的字段映射并加载到了全局的gHotSpotVMStructs结构里。这才使得这些对象能从外部进行反射。真实的字段偏移量和内存布局是在JVM编译的时候生成的,这保证了导出的结构是和JVM的目标操作系统兼容的。
进程外连接。这又是一个值得一提的SA的重量级选手。SA框架中的一个很赞的功能就是它能从进程外部来查看一个活动的JVM。这是通过将SA作为一个操作系统级的调试器连接到目标的JVM来完成 的。具体的实现取决于不同的操作系统,在Linux上的话SA框架会创建一个gdb调试器的连接,在Windows上它用的则是winDbg(这个还会用到Windows Debugging Tools)。调试器框架是可扩展的,如果你想使用别的调试器的话就继承下DebuggerBase就好了。
一旦调试器连接建立起来了,返回的gHotSpotVMStruct地址就会传到调试器进程里面,它就可以查看甚至修改目标的JVM的内部对象了。这就是HSDB连接并调试目标JVM的方法,Java和JVM的代码都能进行调试。
希望这能激起你的兴趣。我个人看来,这个架构是JVM里我喜欢的特性之一。它的优雅和开放令我惊讶不已。我们在构建Takipi的一些实时编码的模块时,它也起了很大的作用,在这里我要对它的设计者们脱帽致敬。
原创文章转载请注明出处:
Java译站
分享到:
相关推荐
JDK提供了如jps(Java进程查看器)、jstat(统计监控工具)、jinfo(配置信息查询工具)、jmap(内存映射工具)、jhat(堆转储分析工具)和jstack(线程堆栈跟踪工具)等,帮助开发者分析和诊断JVM的运行状态。...
- **持久代满异常**:`java.lang.OutOfMemoryError: PermGen space`,可能由于大量动态反射类加载导致,可通过调整`MaxPermSize`参数或更换JVM版本来解决。 - **堆栈溢出异常**:`java.lang.StackOverflowError`,...
10. **调试和日志**:为了便于开发和测试,ocelotter应提供调试工具和日志功能,以便跟踪JVM内部状态和字节码执行过程。 ocelotter项目的源代码位于`ocelotter-master`目录下,包含了实现上述功能的代码文件。通过...
6. **异常处理**:JVM支持异常处理机制,通过try-catch-finally语句块来捕获和处理运行时错误。栈跟踪(StackTrace)是调试程序的重要工具,显示了异常发生时的执行路径。 7. **多线程**:JVM支持多线程编程,每个...
行为分析系统可能有工具来跟踪线程状态,帮助识别这些问题。 5. **日志与调试**: 源码中可能包含了详细的日志记录和调试功能,这对于追踪程序运行过程中的问题非常有用。了解如何合理地设计和使用日志系统,能帮助...
- **运行时动态类型**:通过Class类获取对象的类型信息,动态创建对象和调用方法,反射是Java强大功能之一,但也需要注意性能影响。 7. **异常处理** - **异常分类**:检查异常和运行时异常,如何抛出、捕获和...
- **Session与Cookie**:Session在服务器端存储用户信息,而Cookie在客户端,用于跟踪用户状态。 11. **JVM相关** - **equals与==**:equals用于比较对象内容,==比较对象引用是否相等。 - **hashCode与equals**...
【基础】运行时异常和非运行时异常 参见 21 运行时异常 21 非运行时异常 22 【基础】java引用类型 23 强引用(StrongReference) 23 软引用(SoftReference) 23 弱引用(WeakReference) 23 虚引用...
- **虚引用**(幻象引用):仅用于跟踪对象被垃圾收集的状态,无法通过虚引用来访问对象。 以上就是Java面试中涉及的一些基础知识点,理解并熟练掌握这些内容对于成为一名合格的Java开发者至关重要。在面试中,...
2. 反射机制允许在运行时动态访问和修改类的方法和属性,通过Class类和Method类等,可以获取类对象信息并调用方法。 3. 创建线程主要有两种方式:继承Thread类和实现Runnable接口。Thread类直接继承,Runnable接口...
在Java编程语言中,实时追踪(RealTimeTracking)是一个关键概念,它涉及到程序运行时的监控、调试和性能分析。...开发者可以根据实际需求选择合适的工具,实现对程序运行状态的实时监控,从而提高代码质量和运行效率。
9. **反射**:反射用于在运行时动态获取类的信息并操作类的对象,常见于插件开发、动态代理等场景。 10. **自定义注解**:自定义注解可以为代码添加元数据,用于代码的编译、运行时检测、动态配置等。 11. **HTTP...
- 运行时异常是程序运行时可能遇到的错误,如`NullPointerException`,通常不需显式捕获。而一般异常(检查型异常)如`IOException`,需要在方法签名中声明。 4. **Servlet**: - Servlet生命周期包括初始化...
- Java序列化用于持久化对象,当需要在网络间传输或保存对象状态时使用。 - 动态代理用于在运行时创建代理对象,通常用于实现回调、AOP等。 5. **对象拷贝**: - 对象拷贝可以实现对象的复制,分为浅拷贝(只...
Java反射机制允许在运行时动态获取类的信息并操作。它可以用来在运行时判断任意对象的类,创建对象,访问和修改类的成员变量,以及调用方法。反射机制有助于实现动态编程,如生成动态代理,或者在不了解对象具体类型...
- **平台无关性**:基于JVM运行,使得Java程序可以在任何支持JVM的平台上运行而无需重新编译。 - **高性能**:通过JIT编译器和优化技术,提升了执行效率。 - **多线程**:内置对多线程的支持,可以轻松编写并行...
- **JVM检测**:检查JVM状态,防止在调试环境中运行。 #### 十四、代码混淆 - **工具选择**:使用ProGuard或R8等混淆工具。 - **混淆规则**:制定详细的混淆规则,确保关键逻辑不受影响。 #### 十五、NDK使用 - ...