全部的程序员都会遇到 bug,对于运行态的错误,咱们每每须要一些方法来观察和测试运行态中的环境。在 Java 程序中,最简单的,您是否尝试过使用 System.out.println()
来输出您的 Java 程序的执行中的各类变量状态来发现您的 Java 程序运行时的问题?这种方式方便易用,在一些简单的状况下可以解决您的问题,可是若是当您的程序运行在远程环境上,或者当前环境不容许控制台终端输出(好比,考虑一下虚拟机初始化之时),您没法获取终端输出的时候呢?或者,若是您根本没法本地修改运行您的程序?前端
无须担忧,您能够经过不少的调试工具来帮助您解决这个问题,常见的 IDE 都附带一个很是直观简单的调试工具,好比 Eclipse(图 1)就提供一个功能很是全面,操做很是简单的调试器。程序员
其余的一些常见的 Java IDE,好比 Netbeans 和 IntelliJ 等等也都提供了相似的功能,您甚至能不用 IDE 提供的图形界面,使用 JDK 自带的 jdb 工具,以文本命令的形式来调试您的 Java 程序。这些形形色色的调试器都支持本地和远程的程序调试,那么它们是如何被开发的?它们之间存在着什么样的联系呢?咱们不得不说起 Java 的调试体系—— JPDA 。算法
咱们知道,Java 程序都是运行在 Java 虚拟机上的,咱们要调试 Java 程序,事实上就须要向 Java 虚拟机请求当前运行态的状态,并对虚拟机发出必定的指令,设置一些回调等等,那么 Java 的调试体系,就是虚拟机的一整套用于调试的工具和接口。apache
对于 Java 虚拟机接口熟悉的人来讲,您必定还记得 Java 提供了两个接口体系,JVMPI(Java Virtual Machine Profiler Interface)和 JVMDI(Java Virtual Machine Debug Interface),而它们,以及在 Java SE 5 中准备代替它们的 JVMTI(Java Virtual Machine Tool Interface),都是 Java 平台调试体系(Java Platform Debugger Architecture,JPDA)的重要组成部分。 Java SE 自 1.2.2 版就开始推出 Java 平台调试体系结构(JPDA)工具集,而从 JDK 1.3.x 开始,Java SDK 就提供了对 Java 平台调试体系结构的直接支持。顾名思义,这个体系为开发人员提供了一整套用于调试 Java 程序的 API,是一套用于开发 Java 调试工具的接口和协议。本质上说,它是咱们通向虚拟机,考察虚拟机运行态的一个通道,一套工具。理解这一点对于学习 JPDA 很是重要。编程
换句话说,经过 JPDA 这套接口,咱们就能够开发本身的调试工具。经过这些 JPDA 提供的接口和协议,调试器开发人员就能根据特定开发者的需求,扩展定制 Java 调试应用程序,开发出吸引开发人员使用的调试工具。前面咱们提到的 IDE 调试工具都是基于 JPDA 体系开发的,区别仅仅在于它们可能提供了不一样的图形界面、具备一些不一样的自定义功能。另外,咱们要注意的是,JPDA 是一套标准,任何的 JDK 实现都必须完成这个标准,所以,经过 JPDA 开发出来的调试工具先天具备跨平台、不依赖虚拟机实现、JDK 版本无关等移植优势,所以大部分的调试工具都是基于这个体系的。后端
JPDA 定义了一个完整独立的体系,它由三个相对独立的层次共同组成,并且规定了它们三者之间的交互方式,或者说定义了它们通讯的接口。这三个层次由低到高分别是 Java 虚拟机工具接口(JVMTI),Java 调试线协议(JDWP)以及 Java 调试接口(JDI)。这三个模块把调试过程分解成几个很天然的概念:调试者(debugger)和被调试者(debuggee),以及他们中间的通讯器。被调试者运行于咱们想调试的 Java 虚拟机之上,它能够经过 JVMTI 这个标准接口,监控当前虚拟机的信息;调试者定义了用户可以使用的调试接口,经过这些接口,用户能够对被调试虚拟机发送调试命令,同时调试者接受并显示调试结果。在调试者和被调试着之间,调试命令和调试结果,都是经过 JDWP 的通信协议传输的。全部的命令被封装成 JDWP 命令包,经过传输层发送给被调试者,被调试者接收到 JDWP 命令包后,解析这个命令并转化为 JVMTI 的调用,在被调试者上运行。相似的,JVMTI 的运行结果,被格式化成 JDWP 数据包,发送给调试者并返回给 JDI 调用。而调试器开发人员就是经过 JDI 获得数据,发出指令。图 2 展现了这个过程:缓存
固然,开发人员彻底能够不使用完整的三个层次,而是基于其中的某一个层次开发本身的应用。好比您彻底能够仅仅依靠经过 JVMTI 函数开发一个调试工具,而不使用 JDWP 和 JDI,只使用本身的通信和命令接口。固然,除非是有特殊的需求,利用已有的实现会使您事半功倍,避免重复发明轮子。网络
这三个模块咱们会在后续文章中分别详细介绍,这里咱们简单介绍它们的主要功能:架构
JVMTI(Java Virtual Machine Tool Interface)即指 Java 虚拟机工具接口,它是一套由虚拟机直接提供的 native 接口,它处于整个 JPDA 体系的最底层,全部调试功能本质上都须要经过 JVMTI 来提供。经过这些接口,开发人员不只调试在该虚拟机上运行的 Java 程序,还能查看它们运行的状态,设置回调函数,控制某些环境变量,从而优化程序性能。咱们知道,JVMTI 的前身是 JVMDI 和 JVMPI,它们原来分别被用于提供调试 Java 程序以及 Java 程序调节性能的功能。在 J2SE 5.0 以后 JDK 取代了 JVMDI 和 JVMPI 这两套接口,JVMDI 在最新的 Java SE 6 中已经不提供支持,而 JVMPI 也计划在 Java SE 7 后被完全取代。eclipse
JDWP(Java Debug Wire Protocol)是一个为 Java 调试而设计的一个通信交互协议,它定义了调试器和被调试程序之间传递的信息的格式。在 JPDA 体系中,做为前端(front-end)的调试者(debugger)进程和后端(back-end)的被调试程序(debuggee)进程之间的交互数据的格式就是由 JDWP 来描述的,它详细完整地定义了请求命令、回应数据和错误代码,保证了前端和后端的 JVMTI 和 JDI 的通讯通畅。好比在 Sun 公司提供的实现中,它提供了一个名为 jdwp.dll(jdwp.so)的动态连接库文件,这个动态库文件实现了一个 Agent,它会负责解析前端发出的请求或者命令,并将其转化为 JVMTI 调用,而后将 JVMTI 函数的返回值封装成 JDWP 数据发还给后端。
另外,这里须要注意的是 JDWP 自己并不包括传输层的实现,传输层须要独立实现,可是 JDWP 包括了和传输层交互的严格的定义,就是说,JDWP 协议虽然不规定咱们是经过 EMS 仍是快递运送货物的,可是它规定了咱们传送的货物的摆放的方式。在 Sun 公司提供的 JDK 中,在传输层上,它提供了 socket 方式,以及在 Windows 上的 shared memory 方式。固然,传输层自己无非就是本机内进程间通讯方式和远端通讯方式,用户有兴趣也能够按 JDWP 的标准本身实现。
JDI(Java Debug Interface)是三个模块中最高层的接口,在多数的 JDK 中,它是由 Java 语言实现的。 JDI 由针对前端定义的接口组成,经过它,调试工具开发人员就能经过前端虚拟机上的调试器来远程操控后端虚拟机上被调试程序的运行,JDI 不只能帮助开发人员格式化 JDWP 数据,并且还能为 JDWP 数据传输提供队列、缓存等优化服务。从理论上说,开发人员只需使用 JDWP 和 JVMTI 便可支持跨平台的远程调试,可是直接编写 JDWP 程序费时费力,并且效率不高。所以基于 Java 的 JDI 层的引入,简化了操做,提升了开发人员开发调试程序的效率。
表 1 总结了三个模块的不一样点:
Apache Harmony 旨在开发出一个独立且与现有 JDK 兼容的 Java SE 5 实现,它以 Apache 软件许可证 2.0 版发行。它创建了一个开放的模块化运行时架构,包括虚拟机和类库之间及其内部的模块化,经过这个平台,社区能在已有实现的基础上自由定制本身的 Java 实现,或者对某个模块单独进行创新。
每个虚拟机都应该实现 JVMTI 接口,可是 JDWP 和 JDI 自己与虚拟机并不是是不可分的,这三个层之间是经过标准所定义的交互的接口和协议联系起来的,所以它们能够被独立替换或取代,但不会影响到总体调试工具的开发和使用。所以,开发和使用本身的 JDWP 和 JDI 接口实现是可能的。
Java 软件开发包(SDK)标准版里提供了 JPDA 三个层次的标准实现,事实上,调试工具开发人员还有不少其余开源实现能够选择,好比 Apache Harmony 提供了 JDWP 的实现。而 JDI,咱们能够在 Eclipse 一个子项目 org.eclipse.jdt.debug 里找到其完整的实现(Harmony 也使用了这套实现,做为其 J2SE 类库的一部分)。经过标准协议,Eclipse IDE 的调试工具就能够彻底在 Harmony 的环境上运行。
Java 语言是第一个使用虚拟机概念的流行的编程语言,正是由于虚拟机的存在,使不少事情变得简单而轻松,掌握了虚拟机,就掌握了内存分配、线程管理、即时优化等等运行态。一样的,Java 调试的本质,就是和虚拟机打交道,经过操做虚拟机来达到观察调试咱们本身代码的目的。这个特色决定了 Java 调试接口和之前其余编程语言的巨大区别。
以 C/C++ 的调试为例,目前比较流行的调试工具是 GDB 和微软的 Visual Studio 自带的 debugger,在这种 debugger 中,首先,咱们必须编译一个“ debug ”模式的程序,这个会比实际的 release 模式程序大不少。其次,在调试过程当中,debugger 将会深层接入程序的运行,掌握和控制运行态的一些信息,并将这些信息及时返回。这种介入对运行的效率和内存占用都有必定的需求。基于这些需求,这些 Debugger 自己事实上是提供了,或者说,建立和管理了一个运行态,所以他们的程序算法比较复杂,个头都比较大。对于远端的调试,GDB 也没有很好的默认实现,固然,C/C++ 在这方面也没有特别大的需求。
而 Java 则不一样,因为 Java 的运行态已经被虚拟机所很好地管理,所以做为 Java 的 Debugger 无需再本身创造一个可控的运行态,而仅仅须要去操做虚拟机就能够了。 Java 的 JPDA 就是一套为调试和优化服务的虚拟机的操做工具,其中,JVMTI 是整合在虚拟机中的接口,JDWP 是一个通信层,而 JDI 是前端为开发人员准备好的工具和运行库。
从构架上说,咱们能够把 JPDA 看做成是一个 C/S 体系结构的应用,在这个构架下,咱们能够方便地经过网络,在任意的地点调试另一个虚拟机上的程序,这个就很好地解决了部署和测试的问题,尤为知足解决了不少网络时代中的开发应用的需求。前端和后端的分离,也方便用户开发适合于本身的调试工具。
从效率上看,因为 Java 程序自己就是编译成字节码,运行在虚拟机上的,所以调试先后的程序、内存占用都不会有大变化(仅仅是启动一个 JDWP 所须要的内存),任意程度均可以很好地调试,很是方便。而 JPDA 构架下的几个组成部分,JDWP 和 JDI 都比较小,主要的工做可让虚拟机本身完成。
从灵活性上,Java 调试工具是创建在强大的虚拟机上的,所以,不少前沿的应用,好比动态编译运行,字节码的实时替换等等,均可以经过对虚拟机的改进而获得实现。随着虚拟机技术的逐步发展和深刻,各类不一样种类,不一样应用领域中虚拟机的出现,各类强大的功能的加入,给咱们的调试工具也带来不少新的应用。
总而言之,一个先天的,可控的运行态给 Java 的调试工做,给 Java 调试接口带来了极大的优点和便利。经过 JPDA 这个标准,咱们能够从虚拟机中获得咱们所须要的信息,完成咱们所但愿的操做,更好地开发咱们的程序。
本文简单介绍了 JPDA 的三个模块以及它们如何和其它层次交互,让读者在总体上对 JPDA 体系有了一个直观的了解,从而方便后面针对每一个模块具体介绍的学习,这里咱们学习到:
JPDA 定义了一套如何开发调试工具的接口和规范。
JPDA 由三个独立的模块 JVMTI、JDWP、JDI 组成。
调试者经过 JDI 发送接受调试命令。
JDWP 定义调试者和被调试者交流数据的格式。
JVMTI 能够控制当前虚拟机运行状态。
除了标准实现,JPDA 还有许多开源实现供使用。
Java 调试工具的优势。
JDI— Java 调试接口(Java Debug Interface)
JDT— Java 开发工具(Java Development Tools)
JDWP— Java 调试网络协议(Java Debug Wire Protocol)
JPDA— Java 平台调试器架构(Java Platform Debugger Architecture)
JVM— Java 虚拟机(Java Virtual Machine)
JVMDI— JVM 调试接口(JVM Debug Interface)
JVMTI— JVM 工具接口(JVM Tool Interface)
VM— 虚拟机(Virtual Machine)