在 JDI 中监控类加载
在过去的几周里,我一直在构建一个基于 Java 调试接口的 Java 进程监控工具。虽然我以前做过很多这样的工作,但已经有几年了,所以现在我正在重蹈覆辙。由于我记得细节和陷阱,我一直在发布我的笔记,希望你会发现它们有用。
今天我要谈一下 ClassPrepareEvents,在了解一些背景知识之后。您可能已经知道,您可以将调试器附加到已经运行的 Java 进程,或从调试器启动目标进程本身(使用各种命令行开关)。在我的项目中,我总是会附加到一个正在运行的进程,因为关键是根据需要收集进程数据。 JDI 的 ClassPrepareEvent 有趣的原因在于,当您启动调试目标进程时,或者甚至当您附加到已经运行的进程时,您想要的一些断点很可能位于尚未加载的类中。
在我通常的场景中,我调用 com.sun.jdi.VirtualMachine 的 allClasses() 方法来获取所有加载的 ReferenceTypes 的列表。一种将 ReferenceType 视为 Java 类定义的方法。如果您的 Java 类有内部类,那么 JDI 会将它们分解为单独的 ReferenceType。每个 ReferenceType 都包含一个行位置集合;这些对应于可以设置断点的代码行,并由(除其他外)源代码行号标识。如果一行源代码不能成为断点的目标,那么在 ReferenceType 中将不会有它的行位置。在我的基于调试器的应用程序中,我逐步检查所有 ReferenceType 的行位置,将行位置与断点规范匹配,然后注册我的断点请求。
如您所料,我有一个潜在的问题:如果在我构建断点请求时我需要的类尚未加载,我该怎么办?答案是:JDI 的 ClassPrepareEvent。使用这部分 API 的入口点是 EventRequestManager 的 createClassPrepareRequest() 方法。提出请求后,我们用来等待断点事件的事件监听器循环也可用于等待类准备事件(有关类准备的定义,请参见 JVM 规范)。
我记得我之前开发此 API 的一件事是,这里存在时间风险。您可能希望在遍历当前加载的类列表之前创建类准备请求。原因是你不想掉进这个陷阱:
遍历一组当前加载的类,处理并发出断点请求。
突然,一个你需要的类被加载了!
您注册了 class-prepare 事件并在加载类时开始获取事件,但是您错过了在步骤 #1 和步骤 #3 之间加载的类。
这是另一个可能的陷阱:
报名参加备课活动,以免您被上述问题所困扰。
遍历当前加载的类,根据需要请求断点。
处理新加载的类,根据需要请求断点。
第二种方法的问题是您可能会处理相同的断点两次。为什么?当您遍历当前加载的类时,该列表中的某些类很可能是已出现在您的类准备侦听器中的类。这些问题都不能通过在某处使用同步关键字来解决。
无论您是从调试器启动目标应用程序,还是事后附加到它,您都必须处理此问题的一些变化。我处理它的方式是向我用来定义每个断点规范的类添加一些状态。当找到每个相应的加载类并发出断点请求时,我在规范上设置了一个标志,以便我知道请求已注册。此外,我遵循上面概述的第二种方法(最好有重复而不是错过一个)。如果我从 VM 的 ReferenceType 列表中看到我已经处理过的类的类准备事件,那么我只是跳过它。我对相反的情况做同样的事情,其中我的 ReferenceTypes 列表包含我刚刚在 ClassPrepareEvent 侦听器中处理的 ReferenceTypes。
最后,一个我以前没有研究过的问题(无论是在这个开发工作中,还是在我之前在该领域的开发中)——当一个类被卸载时会发生什么,尤其是一个你已经注册断点请求的类。例如,注册的断点请求会阻止类被卸载吗?如果类甚至没有加载,您是否关心搁浅的断点请求? (答案:是的,我想,如果它被重新加载并且您不再有有效的断点请求)。 JDI 确实有一个 ClassUnloadEvent,您也可以为它注册一个侦听器。正如我所说,我还没有处理过这个(可能的)问题,以前从未见过目标类被卸载,但很高兴知道“有一个 API”。
Follow link for more details