如何拦截 Hotspot JVM 中的内存accesses/changes?

How to intercept memory accesses/changes in the Hotspot JVM?

我想为 Java 开发某种反向调试器(您可以在执行期间后退)。为此,我必须知道 JVM 的初始状态(可以通过核心转储轻松获得)。然后我必须拦截 JVM 正在执行的每个内存访问,这样我就可以得到 JVM 在执行期间所做的事情的时间线,这样我就可以重建 JVM 的每个状态。

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

我想到的一些想法:
- 使用 ptrace,但它真的很慢
- 开发某种简单的虚拟机,其中我 运行 JVM(在来宾 OS 之上),这个虚拟机拦截 JVM 可执行文件的所有内存访问,这类似于VMware 的 Replay debugger feature。问题是我不知道该怎么做,或者根本不知道该怎么做?

实际上,您想监视 Java 对象的变化。在低于 JVM 的级别跟踪内存更改是一种选择。使用

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

对于快照,您可以使用

  • ptrace 用于进程挂起和获取进程内存
  • fork-based asynchronous snapshots using custom code / core dumps(利用内存写时复制,主进程不必挂起)
    • 宽松版本中的最大精度实现策略

该选项的缺点是您还必须跟踪与 Java 堆本身无关的写入(JVM 内部结构、垃圾收集、监视器、库等)。影响 Java 堆的写入代表在任何给定时间进程中发生的所有写入的子集。此外,在没有实际 JVM 代码的情况下,从这些进程 snapshots/dumps 中提取实际 Java 对象也不太直接。

在 JVM 级别监视更改方面,更有利的策略,可以使用

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

对于快照,您可以使用

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

"right" 方法取决于所需的性能特征、目标平台、可移植性(是否可以依赖特定的 JVM implementation/version),以及 precision/resolution(snapshots/sampling [聚合写入] 与检测 [记录每个单独的写入])。

就性能而言,在 JVM 级别进行监控往往更有效,因为只需要考虑实际的 Java 堆写入。将您的监控解决方案集成到 VM 中并利用 GC 写屏障可能是一种低开销解决方案,但也是最不便携的解决方案(绑定到特定的 JVM implementation/version)。

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

就 sampling/snapshotting 而言,实施 JVMTI 代理可能是一个很好的折衷方案。它提供了高可移植性(适用于许多 JVM)和高灵活性(迭代和处理可以根据您的需要定制,而不是依赖于标准的 HPROF 堆转储)。