调试服务器代码这种需求虽然很扯但是也会有需要的时候。尤其是当本地代码和服务器代码明明一致,本地代码没问题但是服务器代码在业务处理时出现莫名其妙的问题的时候,而且还不知道该怎么去排查。

这个时候最简单的方式就是直接 DEBUG 远程代码!!!!

想要调试远程代码有个要求,就是本地和远程代码一定是同一版本(比如 git 每次提交时都会有一个 id)。

同时要保证服务器代码在启动时使用了远程调试参数。

开启服务器 Debug

开启服务器 debug 模式需要在启动时就设置,无法在运行时设置。另外,根据 JDK 版本号的不同具体的命令参数也相应的不同,下面是各版本开启 debug 的具体命令参数:

JDK9 及之后版本

  1. -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9000

注意:address 后面跟的是 *: 加上要暴露的端口号。

JDK5 - 8 版本

  1. -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000

注意:address 后面跟的是端口号。

JDK1.4.X 版本

  1. -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9000

注意:address 后面跟的是端口号。

JDK1.3 及之前版本

  1. -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 镜像,目录文件如下:

  1. $ ls
  2. Dockerfile docker-jvm.jar

其中 Dockerfile 内容如下:

  1. FROM openjdk:8
  2. WORKDIR /app
  3. COPY docker-jvm.jar app.jar
  4. EXPOSE 8080
  5. EXPOSE 9000
  6. ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000"
  7. ENTRYPOINT ["/bin/bash", "-c", "java -server $RMT_DEBUG -jar app.jar"]

只是简单地做测试,所以 Dockerfile 内容很简单。Java 服务端口是 8080,Debug 调试端口是 9000。

开始构建镜像:

  1. $ sudo docker build -t docker-jvm:v1 .
  2. Sending build context to Docker daemon 16.57MB
  3. Step 1/5 : FROM openjdk:8
  4. ---> d61c96e2d100
  5. Step 2/5 : WORKDIR /app
  6. ---> Using cache
  7. ---> 86e84eda655d
  8. Step 3/5 : COPY docker-jvm.jar app.jar
  9. ---> Using cache
  10. ---> 3744ad272c43
  11. Step 4/5 : ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000"
  12. ---> Using cache
  13. ---> 80002b5d3577
  14. Step 5/5 : ENTRYPOINT ["/bin/bash", "-c", "java -server $RMT_DEBUG -jar app.jar"]
  15. ---> Running in 51842783f50e
  16. Removing intermediate container 51842783f50e
  17. ---> 1e1a8da6ed40
  18. Successfully built 1e1a8da6ed40
  19. Successfully tagged docker-jvm:v1

镜像构建成功之后就来运行容器:

  1. $ sudo docker run -d -p 8080:8080 -p 9000:9000 docker-jvm:v1

注意:在运行容器时暴露了两个端口,分别是 8080 和 9000。一个是服务器自身的端口,一个是 debug 用到的端口。

启动日志如下:

  1. Listening for transport dt_socket at address: 9000
  2. . ____ _ __ _ _
  3. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  5. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  6. ' |____| .__|_| |_|_| |_\__, | / / / /
  7. =========|_|==============|___/=/_/_/_/
  8. :: Spring Boot :: (v2.3.7.RELEASE)
  9. 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)
  10. 2021-08-04 06:37:13.779 INFO 1 --- [ main] com.docker.example.DockerJvmApplication : No active profile set, falling back to default profiles: default
  11. 2021-08-04 06:37:16.389 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
  12. 2021-08-04 06:37:16.432 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
  13. 2021-08-04 06:37:16.439 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
  14. 2021-08-04 06:37:16.630 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
  15. 2021-08-04 06:37:16.635 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2708 ms
  16. 2021-08-04 06:37:17.758 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
  17. 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 ''
  18. 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)
  19. Debugger failed to attach: recv failed during handshake: Resource temporarily unavailable
  20. 2021-08-04 06:37:43.015 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
  21. 2021-08-04 06:37:43.016 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
  22. 2021-08-04 06:37:43.040 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 24 ms

注意第一行输出日志:

  1. Listening for transport dt_socket at address: 9000

然后我们使用 curl 测试:

  1. $ curl -XGET 172.17.21.39:8080
  2. Hello World

看到输出信息就表示服务正常,然后我们使用 IDEA Debug:

IDEA Debug 配置

在 IDEA 中打开项目(版本号要与服务器代码一致),打开运行配置,选择 Remote JVM Debug

image.png

配置内容如下:

image.png

之后使用 Debug 运行即可,然后再次请求 curl -XGET 172.17.21.39:8080 IDEA 就自动进入断点了,如下:

image.png

FAQ

我再启动容器时遇到了下面的错误:

  1. $ sudo docker run -p 8080:8080 -p 9000:9000 docker-jvm:v1
  2. ERROR: transport error 202: gethostbyname: unknown host
  3. ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)
  4. JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialized [debugInit.c:750]

然后仔细检查 Dockerfile 中设置的启动命令,发现环境变量 RMT_DEBUG 配置内容如下:

  1. ENV RMT_DEBUG="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9000"

注意 address 值,这是 JDK9 及之后版本的配置方式,而我使用的是 opendjk8 镜像。之后将 RMT_DEBUG 修改为 JDK8 的配置形式就正常运行了~


完结,撒花~