乐趣区

远程Debug Java进程的方法

原文地址
远程 debug 的意思是启动一个 Java 进程,启动一个 debugger 进程,将两者连接起来,利用 debugger 来 debug Java 进程。
事实上目前所有的 IDE 的 debug 功能都是通过远程 debug 方式来实现的,它们都利用了一个叫做 JDPA(Java Platform Debugger Architecture)的技术。
利用 JDPA 我们除了能够在 IDE 开发的时候 debug,也能够将 IDE attach 到一个生产环境上正在运行的 Java 进程做 debug(事实上这两个场景在本质上是一样的)。
下面会用两个例子来说明如何使用 Intellij IDEA 来 debug 一个 Java 进程。
debug 一个简单的 Java 应用
我们做了一个很简单的 Java 应用,它启动后会每隔 2 秒打印出一个不断增长的数字。
源代码在 Github debug-simple-app:

执行 mvn clean package 打包
执行 java -jar target/debug-simple-app.jar 运行

现在我们要用 IDEA 远程 Debug 它。我们先 ctrl+ c 把进程停止掉。
1)把项目导入到 IDEA 里,因为如果没有源码的话我们没有办法打断点
2)按照下面步骤新建一个 Remote Run/Debug Configuration:

选择 Remote

除了改个名字,设定 Use module classpath,其余的选项不需要修改,直接用默认的就行
这里解释一下各种参数:
Debugger mode:debugger 的模式,有两种:attach 和 listen。

attach 的意思是 debugger 连接到被 debug 的 Java 进程,是主动式的。
listen 的意思是 debugger 监听由 Java 进程发送过来的通信,是被动式的。

Host 和 Port 的设定了被 debug 的 Java 进程的 Host 和 Port,实际上这也告诉我们,远程 Debug 是通过网络进行的。
JDK 选项可根据你的不同 JDK 版本来构造不同的 Command line arguments for remote JVM。
Command line arguments for remote JVM 这个文本框你是不能修改的,它告诉了你如果要这个 Java 进程能够被远程 Debug,那么必须添加这些参数才可以。所以你要把这里的参数复制出来,后面会用得着。
Use module classpath,该选项设定使用那个 module 的源代码来 debug。
3)把刚才的 Command line arguments for remote JVM 添加到刚才的运行命令。
像这样:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar target/debug-simple-app.jar
4)点击下图里的 Debug 按钮开始 debug

你会发现 Console 里出现这么一句话 Connected to the target VM, address: ‘localhost:5005’, transport: ‘socket’,这说明 debugger 已经 attach 成功了。
5)在 debug-simple-app 里的代码打个断点看看效果。
debug 一个 tomcat 应用
实际上 debug 一个 tomcat 应用和前面的例子没有什么大的区别。
我们写了一个很简单的 Servlet,它会返回 Hello World 以及被访问的次数。
源代码在 Github debug-tomcat-app:

执行 mvn clean package 打包
把 target/debug-tomcat-app.war 丢到 tomcat
然后访问 http://localhost:8080/debug-tomcat-app/hello 查看结果

现在我们要用 IDEA 来 debug,那么先把 tomcat 停掉。
1)同样需要把项目导入到 IDEA 里
2)执行 tomcat 的 bin/catalina.sh jpda start,让 tomcat 可以被 debug
3)执行 jps -v | grep Bootstrap 找到 Tomcat 进程:
76905 Bootstrap -Djava.util.logging.config.file=…
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djdk.tls.ephemeralDHKeySize=2048
-Djava.protocol.handler.pkgs=org.apache.catalina.webresources
-agentlib:jdwp=transport=dt_socket,address=localhost:8000,server=y,suspend=n
-Dcatalina.base=…
-Dcatalina.home=…
-Djava.io.tmpdir=…
注意上面的 -agentlib…address=localhost:8000 参数,记住这个端口
4)和前面的例子一样,新建一个 Remote Run/Debug Configuration,把端口设定为 8000,然后启动
5)然后打个断点试试
如果你想改变 Tomcat 的端口怎么做?看看 bin/catalina.sh 你会发现这么一段注释
JPDA_TRANSPORT (Optional) JPDA transport used when the “jpda start”
command is executed. The default is “dt_socket”.

JPDA_ADDRESS (Optional) Java runtime options used when the “jpda start”
command is executed. The default is localhost:8000.

JPDA_SUSPEND (Optional) Java runtime options used when the “jpda start”
command is executed. Specifies whether JVM should suspend
execution immediately after startup. Default is “n”.

JPDA_OPTS (Optional) Java runtime options used when the “jpda start”
command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS,
and JPDA_SUSPEND are ignored. Thus, all required jpda
options MUST be specified. The default is:

-agentlib:jdwp=transport=$JPDA_TRANSPORT,
address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND
所以你只需要提供 JPDA_ADDRESS 环境变量参数就行了。比如这样:JPDA_ADDRESS=5005 bin/catalina.sh jpda start
参考文档

Debug your Java code with ease using JPDA
JPDA Connection and Invocation
Oracle VM Invocation Options

退出移动版