Java 随笔程序员

使用JDB直接在远程服务器上进行调试

2017-12-21  本文已影响60人  小鱼爱小虾

程序开发过程中,debug 是必不可少的一部分,它能帮助我们及时发现一些不易察觉的 bug,但并不是所有的 bug 都能有幸在开发过程中就被发现,当程序被部署到远程服务器后,bug 的排查可能就不那么轻松了。

开发过程中常常会发现程序在我们本地运行的时候一切正常,但在测试环境或生产环境会出现不可预测的问题,也就是一些潜在的 bug 在特定的环境下才会暴露出来,可能是数据引起的,也可能是其他不确定的因素。此时通常的方法可能就是通过打印出更加详细的日志再进行分析,而日志的详细粒度也往往不容易把控,过多会增加分析的复杂度,过少又不易于发现问题。总之没有在本地 debug 来的痛快。有同学可能会说,”我们可以让远程 JVM 在启动的时候加载 JDWP Agent,然后在本地 IDE 中指定端口进行远程连接,从而进行调试“。不可否认,这是最理想的方案,但现实往往有点骨感,起码在我们公司,这个过程会让人崩溃。我们所有的服务器都部署在北美,由于网络原因,让 IDE 与远程 JVM 建立连接就需要消耗一点时间,之后服务器上的程序可能早就已经跑到断点了,而本地 IDE 还没有及时反应过来。这种卡顿、延迟现象让调试过程来的相当痛苦,还不如直接去分析日志。

换个思路:

“难道我们不能直接在远程服务器上直接进行调试吗?”

有同学可能会说:

“服务器通常是没有桌面的,如何在上面使用 IDE?”

这其实还是思维的固化,IDE 提供的 Debugger(调试器)其实就是 Java Debug Interface(JDI)的一个实现,比如我们再熟悉不过的Eclipse,它的两个插件org.eclipse.jdt.debug.uiorg.eclipse.jdt.debug,前者是Debugger的界面实现,后者就是JDI的一个完整实现。而 JDK 自带的jdb也是 JDI 的一个实现,所以我们完全可以直接使用这个自带工具进行调试。

Java Debugger(JDB)是一个用来调试Java类文件的命令行工具,它跟 Eclipse、Intellij 等 IDE 里的调试器一样,都是 Java Platform Debugger Architecture - JPDA 三大模块中最高层模块 JDI 的完整实现。

JPDA - Java 调试体系

说到这里,我们有必要先简单了解一下 JPDA。JPDA 由三个相对独立的模块组成,由低到高分别是 JVM 工具接口(JVMTI)、Java 调试线协议(JDWP)、Java 调试接口(JDI),层次结构如下图:

JPDA - Java调试体系

1. JVMTI

处于整个 JPDA 体系的最底层的 Java 虚拟机工具接口,是一套由虚拟机直接提供的 native 接口,由 C 语言实现,所有调试功能本质上都需要通过 JVMTI 来提供。通过这些接口,开发人员不仅调试在该虚拟机上运行的 Java 程序,还能查看它们运行的状态,设置回调函数,控制某些环境变量,从而优化程序性能。

2. JDWP

一个通讯交互协议,定义了调试器与目标虚拟机之间传递的信息的格式,包括请求命令、回应数据和错误代码。同样也是由 C 语言实现。

3. JDI

三个模块中最高层的接口,在多数的 JDK 中,它是由 Java 语言实现的。 通过它,调试工具开发人员就能通过调试器来远程操控目标虚拟机上被调试程序的运行。

Java Debugger(JDB)

下面我们来了解一下这个 JDK 自带工具的使用方法。JDB 提供了多种连接目标程序 JVM 的方式,这里介绍最常用的两种。

1. 由 jdb 命令创建目标 JVM

这种方式下,jdb 命令直接为目标 Java 程序启动一个 JVM,加载类信息,即程序的启动是由 jdb 命令直接触发的,启动成功后,目标 JVM 就会被暂停,等待用户输入命令来让程序得以执行。这就是我们在本地用 IDE 进行 debug 的方式。
比如用 JDB 调试如下程序:

// Test.java

package demo;

public class Test {
    private int base = 1;
  
    public int add(int a) {
        return base + a;
    }
}
// Main.java

package demo;

public class Main {

    public static void main(String[] args) {
        Test t = new Test();
        int result = t.add(2);
        System.out.println(result);
    }

}

编译上面两个源文件后,在控制台进行调试:

更多 JDB 命令请参考 Oracle 官方文档

2. 由 jdb 命令 attach 到已经处于运行状态的目标 JVM

这种方式适用于远程调试,也是我们直接在远程服务器上进行 debug 的方式。 它需要目标 JVM 自身在启动的时候传入一些额外的参数,大致格式如下:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=<PORT> <主类的全路径名> 

其中 address 参数可选,如果不指定的话会随机分配一个可用端口。
为了演示,这里用 Spring Initializr 创建了一个简单的 Web Application : Helloworld,除了生成的代码,新建了一个简单的 HelloworldController.java

package com.example.helloworld.helloworld;

import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;


@Controller
public class HelloworldController {

    private final String message = "helloworld";

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return message;
    }
  
}

这种方式没有因为网络原因而导致的卡顿、延迟现象,但操作起来可能比较复杂,不是很直观,但对于在不改变系统运行环境、又没有详细 log 的情况下快速进行问题的排查还是有一定的帮助的。不过,不要在生产环境使用,因为一旦进入断点,程序就会被中断了

上一篇 下一篇

猜你喜欢

热点阅读