您的位置 首页 java

年薪200万大佬带你揭开JVM断点调试的神秘面纱,建议学习

Java断点调试原理解析

对于断点的使用方式,我们都不陌生: 我们首先需要在IDE里标记上一行代码,然后程序会在执行到这行代码的时候停下,此时我们可以进行观察变量、单步执行、恢复执行等等操作 。那么IDE是如何通知被调试的程序该什么时候停下来?被调试的程序又如何向IDE传递当前变量的信息?市面上形形色色的调试器,都支持这种调试方式,它们是如何被开发的?这就不得不提及JAVA的调试体系—— JPDA

  • JPDA

调试过程存在几个很自然的概念:调试器(debugger)、被调试者(debugee)、通信机制,这几个概念分别对应到了JPDA的三个层次:JAVA调试接口(JDI)、JAVA虚拟机工具接口(JVMTI)、JAVA调试线协议(JDWP)。

年薪200万大佬带你揭开JVM断点调试的神秘面纱,建议学习

  • JDI(Java Debug Interface)

属于调试器的JDI,是三个层次中最高层的接口,它封装了丰富的Java API,来帮助调试器的开发人员可以快速地实现一个调试器,它不仅能帮助开发人员格式化 JDWP 数据,而且还能为 JDWP 数据传输提供队列、缓存等优化服务。

  • JVMTI(Java Virtual Machine Tool Interface)

JVMTI是JAVA SE 5开始,由虚拟机提供的一套native接口,通过这套接口可以控制和获知JVM的运行状态。
关于调试,只是JVMTI提供的功能之一。JVMTI具备四大块功能: 调试、事件处理和回调函数、内存控制和对象获取、线程和锁。

  • JDWP(Java Debug Wire Protocol)

JDWP定义了调试器(debugger)和被调试的 Java 虚拟机(target vm)之间的通信协议。它只定义数据传输格式,不定义传输层如何实现(socket/共享内存)。

  • JPDA中的断点

写了这么多,究竟断点在JPDA中是如何体现的呢?

断点调试有三个个关键的场景:设置断点、恢复执行、断点触发,对于设置断点和恢复执行,在JPDA的层面来看,是十分相似的,所以这一章节,我们只挑一个来分析。

  • 设置断点

对于设置断点,JDI提供了这样一个方法

 com.sun.jdi.request.EventRequestManager#createBreakpointRequest(com.sun.jdi.Location location)  

在JDI中调用这个方法后,数据通过JDWP传输到JVM,并调用JVMTI的设置断点的api:

 JvmtiBreakpoint::set  

一个大体的流程图是这样的:

断点触发

JVMTI向外回调的数据,会被JDWP封装为事件(Event)类型,断点的触发也不例外。当断点发生时,JVMTI就会触发一个JVMTI消息,调用JDWP中预先定义好的处理该事件的回调函数HandleBreakPoint,这个回调函数会生成一个JDWP所描述的断点事件来告知调试器。

这里还要谈到一个细节,断点在触发的时候,需要所有java线程挂起,这一步由谁来完成呢?

之前我们提到了HandleBreakPoint这个函数,我们说它是来处理断点的回调函数,是它的执行线程来完成的吗?显然不是,如果它先挂起所有线程,就无法再向调试器发送数据(因为自身也被挂起);如果它先发送数据,再挂起,就不能保证调试器收到信息的时候,所有java线程真的全被挂起了。

因此,JDWP提供了一个的线程来解决这个问题,叫EventDispatcher,它来进行挂起和发送信息。具体的过程是,JVMTI会把断点消息放到一个消息队列里,EventDispatcher来取,取出后,会根据事件中的设定,来挂起所要求的java线程,随之发出事件。

JVM中的断点

文章开头我们提到过,这篇文章是准备“打破砂锅问到底”的。在上一章节里,我们分析了断点事件在JPDA中的运作原理,但还是有很多问题没有解决。设置过断点之后,JVM中究竟发生了哪些变化?JVM在运行中,好比一个高速行驶的火车,它如何停下来,又如何恢复呢?

这些问题,需要深入源码来找到答案了。本章节以我们接触最多的HotSpot源码为例,解读HotSpot中,对于断点的管理。

特殊的字节码0xCA

在jvm的指令集中,有三个字节码是作为保留操作码,留给虚拟机内部使用的。

  • 0xca breakpoint 调试时的断点标记
  • 0xfe impdep1 为特定软件而预留的语言后门
  • 0xff impdep2 为特定硬件而预留的语言后门

0xca作为一个断点标记,jvm在执行到这个字节码的时候,就代表触发了断点。

所以在设置断点的时候,jvm做的事情,就是根据断点对应的方法和字节码的位置,找到设置断点的位置,然后把正常的字节码替换为0xca,并保存好原来的字节码用于恢复。

断点管理

我们立刻就面临了另一个问题,这些被替换掉的字节码,是如何保存的?

HotSpot中,由BreakpointInfo类来管理单个断点的信息:

 class BreakpointInfo : public CHeapObj<mtClass> {
private:
Bytecodes::Code _orig_bytecode; //原字节码
int _bci; //字节码偏移量,表示字节码在method中的位置
u2 _name_index;
u2 _signature_index; // 用于标识method
BreakpointInfo* _next;
}  

一个类的所有断点,会作为一个链表,被保管在instanceKlass中。关于断点的操作和查询接口,被封装在Method类中,由Method去自身对应的instanceKlass中访问这个断点列表。

注: HotSpot的OOP-Klass 模型中定义,虚拟机在加载class文件时,会在方法区对应的创建一个 instanceKlass ,表示其元数据,包括常量池、字段、方法等。这个instanceKlass,相当于java类在hotspot中的C++对等体。

这些底层能力,最终被JVMTI封装出来,暴露出JvmtiBreakpoint类,提供设置和清除断点的方法。另外,JVMTI还提供了 JvmtiBreakpoints ,主要进行批量操作和断点的缓存。

到此,我们完成了对 HotSpot 中断点管理的一个自下而上的分析。上文提到的C++类,它们之间的关系,如下图所示:

年薪200万大佬带你揭开JVM断点调试的神秘面纱,建议学习

总结

我们已经介绍了在java中断点的原理。还有很多细节问题就不一一叙述了,价值不大。

对于其他语言来说,虽然实现方式会有所不同,但是断点的大体思想是差不多的。比如在X86系统中就是指令INT 3,用它来代替我们希望停止的指令。

写这篇文章,也是由于网上对于jvm断点的原理解释很少,反观C++断点调试原理的文章有很多,可能java天生为我们提供了JVM这样一个强大的平台,导致java程序员接触底层代码的机会确实比C++程序员少很多。


今天分享的内容就到这里了~看到这里的小伙伴,无论你是小白还是大神,都欢迎多多 评论 点赞 互动 哦。

大家也可以将学习使用Java过程中不懂的困惑提出来,后续我会为大家讲解。

最近我也整理了一些Java资料,包含 各大厂面经分享、模拟试题和视频干货 。千万不要错过!如果感兴趣的话,欢迎 关注并私信 我!爱你们~

文章来源:智云一二三科技

文章标题:年薪200万大佬带你揭开JVM断点调试的神秘面纱,建议学习

文章地址:https://www.zhihuclub.com/177906.shtml

关于作者: 智云科技

热门文章

网站地图