如何在 JDI 中调试由自定义 classloader 加载的 class

How to debug a class loaded by a custom classloader in JDI

使用参数 -Xdebug, -agentlib:jdwp=transport=dt_socket, server=y, suspend=n, address=4404 启动目标程序。

使用com.sun.jdi相关的class调试目标程序。 VirtualMachine class 的 classesByName 方法。由自定义 class 加载程序加载的 class 不可用。

在目标中我可以通过

得到 class
Class.forName("Script1", false, clazz.getClassLoader())

在 VirtualMachine class 中,只有方法 :

List<ReferenceType> classesByName(String var1);

我该怎么办?

监控class在 JDI 中加载 在过去的几周里,我一直在构建一个基于 Java 调试接口的 Java 进程监控工具。虽然我以前做过很多这样的工作,但已经过去几年了,所以现在我正在回顾我的步骤。因为我记得细节和陷阱,所以我一直在张贴我的笔记,希望你会发现它们有用。

今天我将在介绍一些背景知识后讨论 ClassPrepareEvents。您可能已经知道,您可以将调试器附加到已经 运行 Java 的进程,或者从调试器启动目标进程本身(使用各种命令行开关)。在我的项目中,我总是会附加到 运行 流程,因为重点是根据需要收集流程数据。 JDI 的 ClassPrepareEvent 有趣的原因在于,当您启动一个调试目标进程时,或者甚至当您附加到一个已经 运行 的进程时,您想要的一些断点可能位于 classes 中尚未加载。

在我通常的情况下,我调用 com.sun.jdi.VirtualMachine 的 allClasses() 方法来获取所有加载的 ReferenceType 的列表。一种将 ReferenceType 视为 Java class 定义的块的方法。如果您的 Java class 具有内部 classes,那么它们将被 JDI 分解为单独的 ReferenceType。每个 ReferenceType 包含行位置的集合;这些对应于可以设置断点的代码行,并由(除其他外)源代码行号标识。如果一行源代码不能成为断点的目标,那么 ReferenceType 中就没有它的行位置。在我基于调试器的应用程序中,我遍历所有 ReferenceType 的行位置,将行位置与断点规范匹配,然后注册我的断点请求。

如你所料,我有一个潜在的问题:如果我需要的 class 在我构建断点请求时尚未加载,我该怎么办?答案是:JDI的ClassPrepareEvent。 API 这部分的使用入口点是 EventRequestManager 的 createClassPrepareRequest() 方法。提出我们的要求后,我们用于等待断点事件的相同事件侦听器循环也可用于等待 class 准备事件(请参阅 JVM 规范以了解 class 准备的定义)。

从我之前对此 API 的开发中,我记得一件事是这里存在时间风险。您可能希望在遍历当前加载的 classes 列表之前创建 class 准备请求。原因是你不想落入这个陷阱: 迭代一组当前加载的 classes,处理和制作断点请求。 突然,一个你需要的 class 被载入了! 您注册了 class-prepare 事件并在加载 classes 时开始获取事件,但是您错过了在步骤 #1 和步骤 #3 之间加载的 class。 这是另一个可能的陷阱: 注册 class-prepare 活动,这样您就不会遇到上述问题。 遍历当前加载的 classes,根据需要请求断点。 处理新加载的 classes,必要时请求断点。 第二种方法的问题是您可能会处理同一个断点两次。为什么?当您遍历当前加载的 classes 时,该列表中的某些 classes 很可能会成为 classes,它们已显示在您的 [=34] 中=]-准备听众。这些问题都不能通过在某处使用 synchronized 关键字来解决。

无论您是从调试器启动目标应用程序还是事后附加到它,您都必须处理这个问题的一些变体。我处理它的方法是向我用来定义每个断点规范的 class 添加一些状态。当找到每个相应的已加载 class 并发出断点请求时,我在规范上设置了一个标志,以便我知道请求已注册。此外,我遵循上面概述的第二种方法(最好有重复项而不是遗漏一个)。如果我看到一个 class-prepare 事件用于 class 我已经从 VM 的 ReferenceType 列表中处理过,那么我只是跳过它。我对相反的情况做同样的事情,在这种情况下,我的 ReferenceTypes 列表包含我刚刚在我的 ClassPrepareEvent 侦听器中处理过的 ReferenceTypes。

最后,一个我以前没有研究过的问题(无论是针对这个开发工作,还是在我之前在这个领域的开发中)——卸载 class 时会发生什么,尤其是 class 您已在其上注册断点请求。例如,注册的断点请求是否会阻止 class 被卸载?如果甚至没有加载 class,您是否关心搁浅的断点请求? (答案:是的,我想,如果它被重新加载并且您不再有有效的断点请求)。 JDI 确实有一个 ClassUnloadEvent,您也可以为其注册一个侦听器。正如我所说,我没有处理过这个(可能的)问题,之前从未见过目标 class 被卸载,但很高兴知道 "there's an API for that"。 Follow link for more details