1. Overview
1.概述
Debugging a remote Java Application can be handy in more than one case.
调试一个远程的Java应用程序在不止一种情况下是很方便的。
In this tutorial, we’ll discover how to do that using JDK’s tooling.
在本教程中,我们将发现如何使用JDK的工具来做到这一点。
2. The Application
2.应用
Let’s start by writing an application. We’ll run it on a remote location and debug it locally through this article:
让我们从编写一个应用程序开始。我们将在一个远程位置上运行它,并通过本文在本地进行调试。
public class OurApplication {
private static String staticString = "Static String";
private String instanceString;
public static void main(String[] args) {
for (int i = 0; i < 1_000_000_000; i++) {
OurApplication app = new OurApplication(i);
System.out.println(app.instanceString);
}
}
public OurApplication(int index) {
this.instanceString = buildInstanceString(index);
}
public String buildInstanceString(int number) {
return number + ". Instance String !";
}
}
Then, we compile it with the -g flag to include all debugging information:
然后,我们用-g标志编译它,以包括所有的调试信息。
javac -g OurApplication.java
3. JDWP: The Java Debug Wire Protocol
3.JDWP:Java调试线协议
The Java Debug Wire Protocol is a protocol used in Java for the communication between a debuggee and a debugger. The debuggee is the application being debugged while the debugger is an application or a process connecting to the application being debugged.
Java调试线协议 是Java中用于调试者和调试器之间通信的协议。调试者是被调试的应用程序,而调试器是连接到被调试的应用程序的一个应用程序或进程。
Both applications either run on the same machine or on different machines. We’ll focus on the latter.
这两个应用程序要么运行在同一台机器上,要么运行在不同的机器上。我们将重点讨论后者。
3.1. JDWP’s Options
3.1.JDWP的选项
We’ll use JDWP in the JVM command-line arguments when launching the debuggee application.
在启动调试程序时,我们将在JVM的命令行参数中使用JDWP。
Its invocation requires a list of options:
它的调用需要一个选项列表。
- transport is the only fully required option. It defines which transport mechanism to use. dt_shmem only works on Windows and if both processes run on the same machine while dt_socket is compatible with all platforms and allows the processes to run on different machines
- server is not a mandatory option. This flag, when on, defines the way it attaches to the debugger. It either exposes the process through the address defined in the address option. Otherwise, JDWP exposes a default one
- suspend defines whether the JVM should suspend and wait for a debugger to attach or not
- address is the option containing the address, generally a port, exposed by the debuggee. It can also represent an address translated as a string of characters (like javadebug if we use server=y without providing an address on Windows)
3.2. Launch Command
3.2.发射命令
Let’s start by launching the remote application. We’ll provide all the options listed earlier:
让我们从启动远程应用程序开始。我们将提供前面列出的所有选项。
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 OurApplication
Until Java 5, the JVM argument runjdwp had to be used together with the other option debug:
在Java 5之前,JVM参数runjdwp必须与其他选项debug一起使用。
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
This way of using JDWP is still supported but will be dropped in future releases. We’ll prefer the usage of the newer notation when possible.
这种使用JDWP的方式仍然被支持,但在未来的版本中会被放弃。在可能的情况下,我们倾向于使用较新的符号。
3.3. Since Java 9
3.3.自Java 9以来
Finally, one of the options of JDWP has changed with the release of version 9 of Java. This is quite a minor change since it only concerns one option but will make a difference if we’re trying to debug a remote application.
最后,JDWP的一个选项随着Java版本9的发布而改变了。这是一个相当小的变化,因为它只涉及一个选项,但如果我们试图调试一个远程应用程序,就会有不同的结果。
This change impacts the way address behaves for remote applications. The older notation address=8000 only applies to localhost. To achieve the old behavior, we’ll use an asterisk with a colon as a prefix for the address (e.g address=*:8000).
这一变化影响了address对远程应用程序的行为方式。旧的符号address=8000只适用于localhost。为了实现旧的行为,我们将使用星号和冒号作为地址的前缀(例如 address=*:8000)。
According to the documentation, this is not secure and it’s recommended to specify the debugger’s IP address whenever possible:
根据文档,这并不安全,建议尽可能地指定调试器的IP地址。
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000
4. JDB: The Java Debugger
4.JDB:Java调试器
JDB, the Java Debugger, is a tool included in the JDK conceived to provide a convenient debugger client from the command-line.
JDB,即Java调试器,是一个包含在JDK中的工具,设想从命令行提供一个方便的调试器客户端。
To launch JDB, we’ll use the attach mode. This mode attaches JDB to a running JVM. Other running modes exist, such as listen or run but are mostly convenient when debugging a locally running application:
为了启动JDB,我们将使用attach模式。这种模式将JDB附加到一个正在运行的JVM上。其他的运行模式也存在,比如listen或run,但在调试本地运行的应用程序时,这些模式大多很方便。
jdb -attach 127.0.0.1:8000
> Initializing jdb ...
4.1. Breakpoints
4.1.分割点
Let’s continue by putting some breakpoints in the application presented in section 1.
让我们继续在第1节介绍的应用程序中设置一些断点。
We’ll set a breakpoint on the constructor:
我们将在构造函数上设置一个断点。
> stop in OurApplication.<init>
We’ll set another one in the static method main, using the fully-qualified name of the String class:
我们将在静态方法main中设置另一个,使用String类的完全限定名称。
> stop in OurApplication.main(java.lang.String[])
Finally, we’ll set the last one on the instance method buildInstanceString:
最后,我们将在实例方法buildInstanceString上设置最后一项。
> stop in OurApplication.buildInstanceString(int)
We should now notice the server application stopping and the following being printed in our debugger console:
现在我们应该注意到服务器应用程序停止了,并且在我们的调试器控制台中打印了以下内容。
> Breakpoint hit: "thread=main", OurApplication.<init>(), line=11 bci=0
Let’s now add a breakpoint on a specific line, the one where the variable app.instanceString is being printed:
现在让我们在一个特定的行上添加一个断点,就是变量app.instanceString被打印的那一行。
> stop at OurApplication:7
We notice that at is used after stop instead of in when the breakpoint is defined on a specific line.
我们注意到,在stop之后使用了at,而不是in,当断点被定义在一个特定的行上。
4.2. Navigate and Evaluate
4.2.导航和评估
Now that we’ve set our breakpoints, let’s use cont to continue the execution of our thread until we reach the breakpoint on line 7.
现在我们已经设置了断点,让我们使用cont来继续执行我们的线程,直到我们到达第7行的断点。
We should see the following printed in the console:
我们应该看到控制台中打印出以下内容。
> Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17
As a reminder, we’ve stopped on the line containing the following piece of code:
作为提醒,我们已经在包含以下代码的那一行停止了。
System.out.println(app.instanceString);
Stopping on this line could have also been done by stopping on the main method and typing step twice. step executes the current line of code and stops the debugger directly on the next line.
在这一行停止也可以通过在main方法上停止并输入step两次来完成。step执行了当前的代码行,并在下一行直接停止调试器。
Now that we’ve stopped, the debugee is evaluating our staticString, the app‘s instanceString, the local variable i and finally taking a look at how to evaluate other expressions.
现在我们停下来了,debugee正在评估我们的staticString、app的instanceString、本地变量i,最后看一下如何评估其他表达式。
Let’s print staticField to the console:
让我们把staticField打印到控制台。
> eval OurApplication.staticString
OurApplication.staticString = "Static String"
We explicitly put the name of the class before the static field.
我们明确地将类的名称放在静态字段之前。
Let’s now print the instance field of app:
现在让我们打印app的实例字段。
> eval app.instanceString
app.instanceString = "68741. Instance String !"
Next, let’s see the variable i:
接下来,让我们看看变量i。
> print i
i = 68741
Unlike the other variables, local variables don’t require to specify a class or an instance. We can also see that print has exactly the same behavior as eval: they both evaluate an expression or a variable.
与其他变量不同,局部变量不需要指定一个类或一个实例。我们还可以看到,print的行为与eval完全相同:它们都是对一个表达式或一个变量进行评估。
We’ll evaluate a new instance of OurApplication for which we’ve passed an integer as a constructor parameter:
我们将评估一个新的OurApplication实例,并为其传递一个整数作为构造函数参数。
> print new OurApplication(10).instanceString
new OurApplication(10).instanceString = "10. Instance String !"
Now that we’ve evaluated all the variables we needed to, we’ll want to delete the breakpoints set earlier and let the thread continue its processing. To achieve this, we’ll use the command clear followed by the breakpoint’s identifier.
现在我们已经评估了所有需要评估的变量,我们想删除之前设置的断点,让线程继续处理。为了达到这个目的,我们将使用clear命令,后面加上断点的标识符。
The identifier is exactly the same as the one used earlier with the command stop:
该标识符与先前用于stop命令的标识符完全相同。
> clear OurApplication:7
Removed: breakpoint OurApplication:7
To verify whether the breakpoint has correctly been removed, we’ll use clear without arguments. This will display the list of existing breakpoints without the one we just deleted:
为了验证断点是否被正确删除,我们将使用不带参数的clear。这将显示现有断点的列表,但不包括我们刚刚删除的那个。
> clear
Breakpoints set:
breakpoint OurApplication.<init>
breakpoint OurApplication.buildInstanceString(int)
breakpoint OurApplication.main(java.lang.String[])
5. Conclusion
5.结论
I:n this quick article, we’ve discovered how to use JDWP together with JDB, both JDK tools.
在这篇简短的文章中,我们发现了如何将JDWP与JDB这两个JDK工具一起使用。
More information on the tooling can, of course, be found in their respective references: JDWP’s and JDB’s – to go deeper into the tooling.