| — | — | — |
| LaunchingConnector | home | 表示 java.home 的值,指向 JRE |
| main | 表示所要执行的 Java 类的类名 |
| options | 表示使用的 Java 命令行参数 |
| suspend | 表示是否在启动目标虚拟机后挂起虚拟机 |
| AttachingConnector
ListeningConnector | hostname | 表示被链接一端的地址 |
| port | 表示被链接一端的端口 |
| timeout | 表示等待链接的时间 |
下面将举一个简单例子,描述如何设置 main 链接参数,并启动目标虚拟机。首先,调用链接器的 defaultArguments() 获取该链接器所支持的一组默认参数,见 清单 5。
清单 5. 获取链接器的默认参数
1 2 |
|
默认参数存储在一个 Key-Value 对的 Map 中,Key 是该链接器参数的唯一标识符(对终端用户不可见),Value 是对应的 Connector.Argument 实例(包括具体参数的信息和默认值)。返回的 Map 不能再新增或者删除元素,只能修改已有元素的值。
然后,从返回的 Map 中获取标识符为 main 的链接器参数,如 清单 6。
清单 6. 返回链接器的 main 参数
1 |
|
最后,将 main 参数值设置为 com.ibm.jdi.test.HelloWorld,以修改后的参数启动目标虚拟机,见 清单 7。
清单 7. 设置 main 参数的值并启动虚拟机
1 2 |
|
JDI 事件请求和处理模块
JDI 事件分类
JDI 的 com.sun.jdi.event 包定义了 18 种事件类型,如 表 3 所示。其中,与 Class 相关的有 ClassPrepareEvent 和 ClassUnloadEvent;与 Method 相关的有 MethodEntryEvent 和 MethodExitEvent;与 Field 相关的有 AccessWatchpointEvent 和 ModificationWatchpointEvent;与虚拟机相关的有 VMDeathEvent,VMDisconnectEvent 和 VMStartEvent 等。
表 3. JDI 中的事件类型
| 事件类型 | 描述 |
| — | — |
| ClassPrepareEvent | 装载某个指定的类所引发的事件 |
| ClassUnloadEvent | 卸载某个指定的类所引发的事件 |
| BreakingpointEvent | 设置断点所引发的事件 |
| ExceptionEvent | 目标虚拟机运行中抛出指定异常所引发的事件 |
| MethodEntryEvent | 进入某个指定方法体时引发的事件 |
| MethodExitEvent | 某个指定方法执行完成后引发的事件 |
| MonitorContendedEnteredEvent | 线程已经进入某个指定 Monitor 资源所引发的事件 |
| MonitorContendedEnterEvent | 线程将要进入某个指定 Monitor 资源所引发的事件 |
| MonitorWaitedEvent | 线程完成对某个指定 Monitor 资源等待所引发的事件 |
| MonitorWaitEvent | 线程开始等待对某个指定 Monitor 资源所引发的事件 |
| StepEvent | 目标应用程序执行下一条指令或者代码行所引发的事件 |
| AccessWatchpointEvent | 查看类的某个指定 Field 所引发的事件 |
| ModificationWatchpointEvent | 修改类的某个指定 Field 值所引发的事件 |
| ThreadDeathEvent | 某个指定线程运行完成所引发的事件 |
| ThreadStartEvent | 某个指定线程开始运行所引发的事件 |
| VMDeathEvent | 目标虚拟机停止运行所以的事件 |
| VMDisconnectEvent | 目标虚拟机与调试器断开链接所引发的事件 |
| VMStartEvent | 目标虚拟机初始化时所引发的事件 |
不同的事件需要被分类地添加到不同的事件集合(EventSet)中,事件集是事件发送的最小单位。事件集一旦创建出来,便不可再被修改。JDI 定义了一些规则,用以规定应该如何将事件分别加入到不同的事件集中:
-
每个 VMStartEvent 事件应该分别加入到单独的一个事件集中;
-
每个 VMDisconnectEvent 事件应该分别加入到单独的一个事件集中;
-
所有的 VMDeathEvent 事件应该加入到同一个事件集中;
-
同一线程的 ThreadStartEvent 事件应该加入到同一事件集中;
-
同一线程的 ThreadDeathEvent 事件应该加入到同一事件集中;
-
同一类型的 ClassPrepareEvent 事件应该加入到同一个事件集中;
-
同一类型的 ClassUnloadEvent 事件应该加入到同一个事件集中;
-
同一 Field 的 AccessWatchpointEvent 事件应该加入到同一个事件集中;
-
同一 Field 的 ModificationWatchpointEvent 事件应该加入到同一个事件集中;
-
同一异常的 ExceptionEvent 事件应该加入到同一个事件集中;
-
同一方法的 MethodExitEvents 事件应该加入到同一个事件集中;
-
同一 Monitor 的 MonitorContendedEnterEvent 事件应该加入到用一个事件集中;
-
同一 Monitor 的 MonitorContendedEnteredEvent 事件应该加入到用一个事件集中;
-
同一 Monitor 的 MonitorWaitEvent 事件应该加入到同一个事件集中
-
同一 Monitor 上的 MonitorWaitedEvent 事件应该加入到同一个事件集中
-
在同一线程执行过程中,具有相同行号信息的 BreakpointEvent、StepEvent 和 MethodEntryEvent 事件应该加入到同一个事件集合中。
生成的事件集将被依次地加入到目标虚拟机的事件队列(EventQueue)中。然后,EventQueue 将这些事件集以“先进先出”策略依次地发送到调试器端。EventQueue 负责管理来自目标虚拟机的事件,一个被调试的目标虚拟机上有且仅有一个 EventQueue 实例。特别地,随着一次事件集的发送,目标虚拟机上可能会有一部分的线程因此而被挂起。如果一直不恢复这些线程,有可能会导致目标虚拟机挂机。因此,在处理好一个事件集中的事件后,建议调用事件集的 resume() 方法,恢复所有可能被挂起的线程。
JDI 事件请求
Event 是 JDI 中所有事件接口的父接口,它只定义了一个 request() 方法,用以返回由调试器发出的针对该事件的事件请求(EventRequest)。事件请求是由调试器向目标虚拟机发出的,目的是请求目标虚拟机在发生指定的事件后通知调试器。只有当调试器发出的请求与目标虚拟机上发生的事件契合时,这些事件才会被分发到各个事件集,进而等待发送至调试器端。在 JDI 中,每一种事件类型都对应着一种事件请求类型。一次事件请求可能对应有多个事件实例,但不是每个事件实例都存在与之对应的事件请求。例如,对于某些事件(如 VMDeathEvent,VMDisconnectEvent 等),即使没有对应的事件请求,这些事件也必定会被发送给调试器端。
另外,事件请求还支持过滤功能。通过给 EventRequest 实例添加过滤器(Filter),可以进一步筛选出调试器真正感兴趣的事件实例。事件请求支持多重过滤,通过 EventRequest 的 add*Filter() 方法可以添加多个过滤器。多个过滤器将共同作用,最终只有满足所有过滤条件的事件实例才会被发给调试器。常用的过滤器有:
-
线程过滤器:用以过滤出指定线程中发生的事件;
-
类型过滤器:用以过滤出指定类型中发生的事件;
-
实例过滤器:用以过滤出指定实例中发生的事件;
-
计数过滤器:用以过滤出发生一定次数的事件;
过滤器提供了一些附加的限制条件,减少了最终加入到事件队列的事件数量,从而提高了调试性能。除了过滤功能,还可以通过它的 setSuspendPolicy(int) 设置是否需要在事件发生后挂起目标虚拟机。
事件请求是由事件请求管理器(EventRequestManager)进行统一管理的,包括对请求的创建和删除。一个目标虚拟机中有且仅有一个 EventRequestManager 实例。通常,一个事件请求实例有两种状态:激活态和非激活态。非激活态的事件请求将不起任何作用,即使目标虚拟机上有满足此请求的事件发生,目标虚拟机将不做停留,继续执行下一条指令。由 EventRequestManager 新建的事件请求都是非激活的,需要调用 setEnable(true) 方法激活该请求,而通过 setEnable(false) 则可废除该请求,使其转化为非激活态。
JDI 事件处理
下面将介绍 JDI 中调试器与目标虚拟机事件交互的方式。首先,调试器调用目标虚拟机的 eventQueue() 和 eventRequestManager() 分别获取唯一的 EventQueue 实例和 EventRequestManager 实例。然后,通过 EventRequestManager 的 createXxxRequest() 创建需要的事件请求,并添加过滤器和设置挂起策略。接着,调试器将从 EventQueue 获取来自目标虚拟机的事件实例。
一个事件实例中包含着事件发生时目标虚拟机的一些状态信息。以 BreakpointEvent 为例:
调用 BreakpointEvent 的 thread() 可以获取产生事件的线程镜像(ThreadReference),调用 ThreadReference 的 frame(int) 可获得当前代码行所在的堆栈(StackFrame),调用 StackFrame 的 visibleVariables() 可获取当前堆栈中的所有本地变量(LocaleVariable)。通过调用 BreakpointEvent 的 location() 可获得断点所在的代码行号(Location),调用 Location 的 method() 可获得当前代码行所归属的方法。通过以上调用,调试器便可获得了目标虚拟机上线程、对象、变量等镜像信息。
另外,根据从事件实例中获取的以上信息,调试器还可以进一步控制目标虚拟机。例如,可以调用 ObjectReference 的 getValue() 和 setValue() 访问和修改对象中封装的 Field 或者 LocalVariable 等,进而影响虚拟机的行为。更多的 JDI 的事件处理的详情,请参见图 2。
图 2. JDI 事件处理框架(查看大图)
一个 JDI 的简单实例
下面给出一个简单例子,说明如何实现 JDI 的部分接口来提供一个简易的调试客户端。首先是被调试的 Java 类,这里给出一个简单的 Hello World 程序,main 方法第一行声明一个“Hello World!”的字符串变量,第二行打印出这个字符串的内容,见 清单 8。
清单 8. HelloWorld 类文件
1 2 3 4 5 6 7 8 |
|
接着是一个简单的调试器实现 SimpleDebugger,清单 9 列出了实现该调试器所需要导入的类库和变量。简单起见,所有的变量都声明为静态全局变量。这些变量分别代表了目标虚拟机镜像,目标虚拟机所在的进程,目标虚拟机的事件请求管理器和事件对列。变量 vmExit 标志目标虚拟机是否中止。
清单 9. SimpleDebugger 导入的类和声明的全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
随后是 SimpleDebugger 的 main() 方法,见 清单 10。首先从 VirtualMachineManager 获取默认的 LaunchingConnector,然后从该 Connector 取得默认的参数。接着,设置 main 和 suspend 参数,使得目标虚拟机运行 com.ibm.jdi.test.HelloWorld 类,并随后进入挂起状态。下一步,调用 LaunchingConnector.launch() 启动目标虚拟机,返回目标虚拟机的镜像实例,并且获取运行目标虚拟机的进程( Process)。
然后,创建一个 ClassPrepareRequest 事件请求。当 com.ibm.jdi.test.HelloWorld 被装载时,目标虚拟机将发送对应的 ClassPrepareEvent 事件。事件处理完成后,通过 process 的 destroy() 方法销毁目标虚拟机进程,结束调试工作。
清单 10. SimpleDebugger 的 main() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
下面是 eventLoop() 函数的实现:首先获取目标虚拟机的事件队列,然后依次处理队列中的每个事件。当 vmExit(初始值为 false)标志为 true 时,结束循环。
清单 11. SimpleDebugger 的 eventLoop() 的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
具体事件的处理是由 execute(Event) 实现的,这里主要列举出 ClassPreparEvent 和 BreakpointEvent 事件的处理用法,请参见 清单 12。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后
很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。
我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。
不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
有帮助,可以添加V获取:vip1024b (备注Java)**
[外链图片转存中…(img-EKiwaVno-1711697873909)]
最后
很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。
我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。
不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~