调试服务器代码这种需求虽然很扯但是也会有需要的时候。尤其是当本地代码和服务器代码明明一致,本地代码没问题但是服务器代码在业务处理时出现莫名其妙的问题的时候,而且还不知道该怎么去排查。
这个时候最简单的方式就是直接 DEBUG 远程代码!!!!
想要调试远程代码有个要求,就是本地和远程代码一定是同一版本(比如 git 每次提交时都会有一个 id)。
同时要保证服务器代码在启动时使用了远程调试参数。
开启服务器 Debug
开启服务器 debug 模式需要在启动时就设置,无法在运行时设置。另外,根据 JDK 版本号的不同具体的命令参数也相应的不同,下面是各版本开启 debug 的具体命令参数:
JDK9 及之后版本
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9000
注意:address
后面跟的是 *:
加上要暴露的端口号。
JDK5 - 8 版本
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000
注意:address
后面跟的是端口号。
JDK1.4.X 版本
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9000
JDK1.3 及之前版本
-Xnoagent -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9000
注意:address
后面跟的是端口号。
下面就以 Docker 运行 Java Web 服务做测试。
构建 Docker 镜像
在远程服务器(172.17.21.39) 构建一个 Docker 镜像,目录文件如下:
$ ls
Dockerfile docker-jvm.jar
其中 Dockerfile 内容如下:
FROM openjdk:8
WORKDIR /app
COPY docker-jvm.jar app.jar
EXPOSE 8080
EXPOSE 9000
ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000"
ENTRYPOINT ["/bin/bash", "-c", "java -server $RMT_DEBUG -jar app.jar"]
只是简单地做测试,所以 Dockerfile 内容很简单。Java 服务端口是 8080,Debug 调试端口是 9000。
开始构建镜像:
$ sudo docker build -t docker-jvm:v1 .
Sending build context to Docker daemon 16.57MB
Step 1/5 : FROM openjdk:8
---> d61c96e2d100
Step 2/5 : WORKDIR /app
---> Using cache
---> 86e84eda655d
Step 3/5 : COPY docker-jvm.jar app.jar
---> Using cache
---> 3744ad272c43
Step 4/5 : ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000"
---> Using cache
---> 80002b5d3577
Step 5/5 : ENTRYPOINT ["/bin/bash", "-c", "java -server $RMT_DEBUG -jar app.jar"]
---> Running in 51842783f50e
Removing intermediate container 51842783f50e
---> 1e1a8da6ed40
Successfully built 1e1a8da6ed40
Successfully tagged docker-jvm:v1
镜像构建成功之后就来运行容器:
$ sudo docker run -d -p 8080:8080 -p 9000:9000 docker-jvm:v1
注意:在运行容器时暴露了两个端口,分别是 8080 和 9000。一个是服务器自身的端口,一个是 debug 用到的端口。
启动日志如下:
Listening for transport dt_socket at address: 9000
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.7.RELEASE)
2021-08-04 06:37:13.769 INFO 1 --- [ main] com.docker.example.DockerJvmApplication : Starting DockerJvmApplication on 2fea837e7d50 with PID 1 (/app/app.jar started by root in /app)
2021-08-04 06:37:13.779 INFO 1 --- [ main] com.docker.example.DockerJvmApplication : No active profile set, falling back to default profiles: default
2021-08-04 06:37:16.389 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-08-04 06:37:16.432 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-08-04 06:37:16.439 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-08-04 06:37:16.630 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-08-04 06:37:16.635 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2708 ms
2021-08-04 06:37:17.758 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-08-04 06:37:18.215 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-04 06:37:18.239 INFO 1 --- [ main] com.docker.example.DockerJvmApplication : Started DockerJvmApplication in 5.784 seconds (JVM running for 6.901)
Debugger failed to attach: recv failed during handshake: Resource temporarily unavailable
2021-08-04 06:37:43.015 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-08-04 06:37:43.016 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-08-04 06:37:43.040 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 24 ms
注意第一行输出日志:
Listening for transport dt_socket at address: 9000
然后我们使用 curl 测试:
$ curl -XGET 172.17.21.39:8080
Hello World
看到输出信息就表示服务正常,然后我们使用 IDEA Debug:
IDEA Debug 配置
在 IDEA 中打开项目(版本号要与服务器代码一致),打开运行配置,选择 Remote JVM Debug:
配置内容如下:
之后使用 Debug 运行即可,然后再次请求 curl -XGET 172.17.21.39:8080
IDEA 就自动进入断点了,如下:
FAQ
我再启动容器时遇到了下面的错误:
$ sudo docker run -p 8080:8080 -p 9000:9000 docker-jvm:v1
ERROR: transport error 202: gethostbyname: unknown host
ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)
JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialized [debugInit.c:750]
然后仔细检查 Dockerfile 中设置的启动命令,发现环境变量 RMT_DEBUG 配置内容如下:
ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9000"
注意 address
值,这是 JDK9 及之后版本的配置方式,而我使用的是 opendjk8 镜像。之后将 RMT_DEBUG 修改为 JDK8 的配置形式就正常运行了~
完结,撒花~