原理Java远程调试的原理是两个VM之间通过debug协议进行通信,然后以达到远程调试的目的,两者之间可以通过socket进行通信 调试体系JPDAJPDA(Java Platform Debugger Architecture)是 sun 公司开发的 java平台调试体系,它主要有三个层次组成,即 Java 虚拟机工具接口(JVMTI),Java 调试线协议(JDWP)以及 Java 调试接口(JDI) JVMTI(JVMDI): jdk1.4 之前称为JVMDI,之后改为了JVMTI,它是虚拟机的本地接口,其相当于 Thread 的 sleep、yield native 方法 JDWP(Java Debug Wire Protocol):java调试网络协议,其描述了调试信息的格式,以及在被调试的进程(server)和调试器(client)之间传输的请求 JDI:java调试接口,虚拟机的高级接口,调试器(client)自己实现 JDI 接口,比如 idea、eclipse 等 下面使用两张图更直观的了解JPDA的三个模块层次 1、JPDA模块层次 2、JPDA层次比较 idea 或者 eclipse 调试原理当我们在 idea 或者 eclipse 中以 debug 模式启动运行类,就可以直接调试了,这其中的原理令人不解,下面就给大家介绍一下 客户端(idea 、eclipse 等)之所以可以进行调试,是由于客户端 和 服务端(程序端)进行了 socket 通信,通信过程如下: 1、先建立起了 socket 连接 2、将断点位置创建了断点事件通过 JDI 接口传给了 服务端(程序端)的 VM,VM 调用 suspend 将 VM 挂起 3、VM 挂起之后将客户端需要获取的 VM 信息返回给客户端,返回之后 VM resume 恢复其运行状态 4、客户端获取到 VM 返回的信息之后可以通过不同的方式展示给客户 上述过程便是一个完整的 debug 调试过程,下面通过示例来进一步说明一下这个过程 使用 idea debug 调试一个类,过程如下图: idea 和 程序之间建立了 socket 连接,ip 是 本机,端口是 52690,注意这个端口不是固定的,每次都会变动 cmd 中使用 netstat -ano | findstr 52690 查看该监听端口 52690 使用的进程 上图可以看出,idea 调试客户端 进程id 是 5472,程序调试服务器端 进程id 是 27600,两者之间建立了连接进行通信 服务端之所以可以和客户端建立起连接,是由于调试服务器端加了一句话,打开了调试,如下图: cmd 中 使用 jps -v | findstr HelloWorld 查找进程信息 上图看出,HelloWorld 程序的进程id 确实是 27600,并在启动时添加了以下这句话: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:52690,suspend=y,server=n vm 挂起后将调试信息返回给客户端,客户端可以展示给用户,如下图: debug调试示例demo我们下面使用示例代码来调试运行一下某行代码的某个变量值,如下图: 1、新建调试程序代码
2、调试程序客户端代码
3、下载 jdi.jar 包,然后导入到工程中 4、运行测试,步骤如下 1)、cmd 切换到项目的根目录下,如下图: 2)、编译文件 cmd 执行 javac -g -cp "D:\Program Files\Java\jdk1.8.0_181\lib\tools.jar" com\demo\debug\*.java 命令,如下图: 3)、运行文件 cmd 执行 java -cp ".;D:\Program Files\Java\jdk1.8.0_181\lib\tools.jar" com.demo.debug.SimpleDebugger 命令,运行结果如下图: 调试服务器VM调试处理机制这里有个问题需要思考一下,当debug 时,VM 是如何处理是否有断点的呢?基本上有两种猜想:一是 VM 执行代码的时候主动检查这行代码是否有断点需要处理,二是客户端动态修改了编译文件的字节码,在需要断点的地方加上了标识 对于第一种猜想,需要看 JVM 的 C 语言源码,目前这个先搁置放一下,对于第二种猜想比较好验证,只需要动态dump出类的class文件即可 dump 出 HelloWorld 文件的 class 文件步骤: 1、下载 dumpclass 文件,放到新建的一个目录下,如图: 2、cmd 中使用 jps 命令查看应用进程id号,如图: 3、cmd 切换到此目录下执行 java -cp "$JAVA_HOME\lib\sa-jdi.jar" -jar dumpclass.jar -p 1448 *HelloWorld 命令,如下图: 反编译 dump出来的 HelloWorld.class 文件,如下图: 结论:VM 是通过主动的方式检查执行的每行代码是否有断点需要处理 如何远程(remote)调试使用IDE调试是大家最常用的方式,比如idea、eclipse等,运行时候选择debug模式即可,那么如果使用远程调试怎么做的呢?其实很简单,就是启动项目时加上一些参数而已 一、spring web 项目 小于 tomcat9 版本 tomcat 中 bin/catalina.sh 中增加 CATALINA_OPTS='-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18006',如下图所示: 大于等于 tomcat9 版本 tomcat 中 bin/catalina.sh 中的 JPDA_ADDRESS="localhost:8000" 这一句中的localhost修改为0.0.0.0(允许所有ip连接到8000端口,而不仅是本地)8000是端口,端口号可以任意修改成没有占用的即可,如下图所示: 修改之后使用 sh catalina.sh jpda start 命令启动tomcat 即可 二、spring boot 项目远程调试 -jar 后面添加这样的参数,实例如下图: jdk1.5 之前 -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n jdk1.5 之后 -agentlib:jdwp=transport=dt_socket,address=8800,server=y,suspend=n 上面参数配置好之后,使用 IDEA 进行远程调试,如下图: 1、配置好 remote 远程调试 2、启动调试后请求,如下图: 调试参数详解-Xdebug :启用调试特性 -Xrunjdwp: <sub-options> 在目标 VM 中加载 JDWP 实现。它通过传输和 JDWP 协议与独立的调试器应用程序通信。下面介绍一些特定的子选项 从 Java V5 开始,您可以使用 -agentlib:jdwp 选项,而不是 -Xdebug 和 -Xrunjdwp。但如果连接到 V5 以前的 VM,只能选择 -Xdebug 和 -Xrunjdwp。下面简单描述 -Xrunjdwp 子选项。 -Djava.compiler=NONE: 禁止 JIT 编译器的加载 transport : 传输方式,有 socket 和 shared memory 两种,我们通常使用 socket(套接字)传输,但是在 Windows 平台上也可以使用shared memory(共享内存)传输。 server(y/n): VM 是否需要作为调试服务器执行 address: 调试服务器的端口号,客户端用来连接服务器的端口号 suspend(y/n):值是 y 或者 n,若为 y,启动时候自己程序的 VM 将会暂停(挂起),直到客户端进行连接,若为 n,自己程序的 VM 不会挂起 上面的参数具体可以参看 IDEA 中的,如下图: 参考文档如下: JPDA调试体系:https://www.ibm.com/developerworks/cn/java/j-lo-jpda1/index.html Java 调试接口(JDI):https://www.ibm.com/developerworks/cn/java/j-lo-jpda4/index.html 示例demo:http:///java-debug-interface-api-jdi-hello-world-example-programmatic-debugging-for-beginners/ dump出类的class文件:https://blog.csdn.net/hengyunabc/article/details/51106980 |
|