如何拦截热点JVM中的内存访问/更改?



我想为Java开发某种反向调试器(您可以在执行过程中退后一步(。为此,我必须知道 JVM 的初始状态(可以通过核心转储轻松获取(。然后,我必须拦截 JVM 正在执行的每个内存访问,以便我可以了解 JVM 在执行期间一直在做什么的时间线,以便我可以重建 JVM 的每个状态。

因此,我需要的是一种拦截内存访问但性能开销较低的方法,这意味着该解决方案不应为 JVM 执行增加超过 200-300% 的开销,这已经很多了。

我想到的一些想法:-
使用 ptrace,但它真的很慢
- 开发某种简单的虚拟机,我在其中运行 JVM(在来宾操作系统之上(,并且这个虚拟机拦截了 JVM 可执行文件的所有内存访问,这将类似于 VMware 的重播调试器功能。问题是我不知道该怎么做,或者是否有可能?

实际上,您希望监视 Java 对象的更改。在低于 JVM 的级别跟踪内存更改是一种选择。可以使用以下方法实现最大精度

  • 页面写保护和用于生成写入通知的信号处理程序(必须注意不要干扰 GC 写入屏障(
  • 使用 Valgrind 等检测框架的动态检测(静态检测不是一个选项,因为它不涵盖 JIT 输出(
  • 基于自定义虚拟机管理程序的虚拟化

对于快照,您可以使用

  • ptrace进程暂停和访问进程内存
  • 使用自定义代码/核心转储的基于fork的异步快照(利用写入时内存复制,主进程不必挂起(
    • 宽松版本中的最大精度实施策略

该选项的缺点是,您还将被迫跟踪与 Java 堆本身无关的写入(JVM 内部、垃圾回收、监视器、库等(。影响 Java 堆的写操作表示在任何给定时间进程中发生的所有写操作的子集。此外,在没有实际 JVM 代码的情况下,从这些进程快照/转储中提取实际的 Java 对象就不那么简单了。

在JVM级别监控变化方面,可以使用更有利的策略,实现最大精度

  • 字节码检测(不包括基于 JNI 的写入(
    • 高开销方法:记录每次写入
    • 低开销方法:添加一个写入屏障,该屏障在发生写入时设置标志,并定期转储标记的对象
  • 包含您自己的监控层的自定义 OpenJDK 构建
    • 可以利用垃圾回收器写入屏障来识别更改
      • 通常通过在每次写入或
      • 仅在第一次写入时设置的标志,方法是对与对象关联的内存页进行写保护,并通过设置标志处理分段错误

对于快照,您可以使用

  • 基于 JVMTIIterateThroughHeap和/或FollowReferences的自定义堆快照
  • 使用 JMX 在外部或内部触发的堆转储:
HotSpotDiagnosticMXBean mxbean = ManagementFactory.newPlatformMXBeanProxy(
ManagementFactory.getPlatformMBeanServer(),
"com.sun.management:type=HotSpotDiagnostic",
HotSpotDiagnosticMXBean.class);
mxbean.dumpHeap("dump.hprof", true);
  • 宽松版本中的最大精度实施策略

"正确"的方法取决于所需的性能特征、目标平台、可移植性(是否可以依赖于特定的 JVM 实现/版本(和精度/分辨率(快照/采样 [聚合写入] 与检测 [记录每个单独的写入](。

在性能方面,在JVM级别进行监视往往更有效,因为只需要考虑实际的Java堆写入。将监视解决方案集成到 VM 中并利用 GC 写入屏障可能是一种低开销的解决方案,但也是最不便携的解决方案(与特定的 JVM 实现/版本相关联(。

如果需要记录每个单独的写入,则必须采用检测路线,并且很可能会产生很大的运行时开销。您无法聚合写入,因此没有优化潜力。

在采样/快照方面,实现 JVMTI 代理可能是一个很好的折衷方案。它提供了高可移植性(适用于许多 JVM(和高灵活性(迭代和处理可以根据您的需求进行定制,而不是依赖于标准的 HPROF 堆转储(。

最新更新