JPDA (Java Platform Debugger Architecture) は、Java Platform Debugger Architecture の略称です。 、JPDA が提供する API を使用して、開発者は Java デバッグ アプリケーションを簡単かつ柔軟に構築できます。
JPDA は主に、Java 仮想マシン ツール インターフェイス (JVMTI)、Java デバッグ ワイヤ プロトコル (JDWP)、および Java デバッグ インターフェイス (JDI) の 3 つの部分で構成されます。
Java プログラムはすべて Java 仮想マシン上で実行されます。Java プログラムをデバッグしたい場合は、実際に Java 仮想マシンから現在の実行ステータスを要求し、仮想マシンに特定の命令を発行し、設定する必要があります。コールバックなどの場合、Java デバッグ システムは、仮想マシンをデバッグするためのツールとインターフェイスの完全なセットです。
1: エディターはクライアントおよびサーバー プログラムとして機能し、公開されたリスニング ポートを介してソケット接続を確立します
2: IDE クライアント ブレークポイント イベントがブレークポイントの場所に作成され、JDI インターフェイスを介してサーバー (プログラム) の VM に渡されます。VM は、suspend を呼び出して VM を一時停止します。
3: VM が一時停止された後, クライアントが取得する必要がある VM 情報は、クライアントに戻ります。戻った後、VM 再開は実行状態を再開します。
#4: クライアントは、VM から返された情報を取得した後、それを次の形式で表示できます。さまざまな方法(3) アーキテクチャ システムJPDA は、比較的独立した 3 つの層から構成される完全で独立したシステムを定義し、それらの間の相互作用モードを規定したり、それらの層のインターフェイスを定義したりします。コミュニケーション。 3 つのレベルは、低位から高位まで、Java Virtual Machine Tool Interface (JVMTI)、Java Debug Wire Protocol (JDWP)、および Java Debug Interface (JDI) です。 これら 3 つのモジュールは、デバッグ プロセスをいくつかの自然な概念、つまりデバッガー (デバッガー) とデバッグ対象 (デバッグ対象)、およびそれらの間のコミュニケーターに分解します。 デバッグ対象は、デバッグする Java 仮想マシン上で実行されます。デバッグ対象は、JVMTI 標準インターフェイスを通じて現在の仮想マシンの情報を監視できます。デバッガは、ユーザーがこれらのインターフェイスを通じて使用できるデバッグ インターフェイスを定義します。ユーザーはデバッグ対象の仮想マシンにデバッグ コマンドを送信でき、デバッガーはデバッグ結果を受け入れて表示します。 JDWP 通信プロトコルは、デバッグ コマンドとデバッグ結果の送信に使用され、デバッグ中にデバッガとデバッグ対象を接続します。すべてのコマンドは JDWP コマンド パッケージにカプセル化され、トランスポート層を介してデバッグ対象に送信されます。デバッグ対象は JDWP コマンド パッケージを受信すると、コマンドを解析して JVMTI 呼び出しに変換し、デバッグ対象で実行されます。 同様の状況として、JVMTI が実行結果を JDWP データ パケットの形式に変換し、その結果をデバッガに送信して、JDI 呼び出しに返します。デバッガ開発者は、JDI を通じてデータを取得し、命令を発行します。 上の図に示すように、JPDA は 3 つの層で構成されています。JVM TI - Java VM ツール インターフェイス。 VM によって提供されるデバッグ サービスを定義します。
JDWP - Java デバッグ通信プロトコル。デバッグ対象とデバッガプロセス間の通信を定義します。
#JDI - Java デバッグ インターフェイス。ツール開発者がリモート デバッガー アプリケーションを作成するために簡単に使用できる高レベルの Java 言語インターフェイスを定義します。
/** * 测试程序 * @author zhangyu * @date 2022/2/17 */ @SpringBootApplication @RestController public class DebuggerApplication { public static void main(String[] args) { SpringApplication.run(DebuggerApplication.class, args); } @GetMapping("/test") public String test(){ System.out.println(111); System.out.println(222); return "OK"; } }[4] デプロイメント プログラムの起動パラメータは次のとおりです。
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888 -jar debugger-0.0.1-SNAPSHOT.jarここで、アドレス= 8888 は、リモート デバッグ用のソケット通信ポートとしてポート 8888 を開くことを意味します。 Tomcat の下にデプロイされた通常の Web プロジェクトの場合は、次を参照してください。 Tomcat9 バージョン未満
tomcat 中 bin/catalina.sh 中增加 CATALINA_OPTS=‘-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18006’
如下图所示:
大于等于 tomcat9 版本
tomcat 中 bin/catalina.sh 中的 JPDA_ADDRESS=“localhost:8000” 这一句中的localhost修改为0.0.0.0(允许所有ip连接到8000端口,而不仅是本地)8000是端口,端口号可以任意修改成没有占用的即可
如下图所示:
【5】测试部署的程序正常后,下面构建客户端远程调试,当前以IDEA工具作为客户端
参考:
【1】-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8888
【2】Host:远程服务器地址
【3】Port:远程服务器开放的调试通信端口,非应用端口
测试接口:http://XXX:9999/test。注意本地代码需要和远程部署程序一致。
通过上图可以看到客户端设置断点已经生效,其中在客户端执行了一个调试输出,这个是自定义输出的内容服务器程序并没有,在执行后右侧的服务器控制台日志输出了该信息,因此远程Debug是正常通信和处理的。
(一)调试参数详解
-Xdebug
:启用调试特性
-Xrunjdwp
:在目标 VM 中加载 JDWP 实现。它通过传输和 JDWP 协议与独立的调试器应用程序通信。下面介绍一些特定的子选项
从 Java V5 开始,您可以使用 -agentlib:jdwp 选项,而不是 -Xdebug 和 -Xrunjdwp。如果连接到VM版本低于V5的情况下,只能使用 -Xdebug 和 -Xrunjdwp 选项。下面简单描述 -Xrunjdwp 子选项。
-Djava.compiler=NONE
: 禁止 JIT 编译器的加载
transport
: 传输方式,有 socket 和 shared memory 两种,我们通常使用 socket(套接字)传输,但是在 Windows 平台上也可以使用shared memory(共享内存)传输。
server(y/n)
: VM 是否需要作为调试服务器执行
address
: 调试服务器的端口号,客户端用来连接服务器的端口号
suspend(y/n)
:值是 y 或者 n,若为 y,启动时候自己程序的 VM 将会暂停(挂起),直到客户端进行连接,若为 n,自己程序的 VM 不会挂起
(1)被调试程序
创建一个SpringBoot的WEB项目,提供一个简单的测试接口,并在测试方法中提供一些方法参数变量和局部变量作为后面的调试测试用。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DebuggerApplication { public static void main(String[] args) { SpringApplication.run(DebuggerApplication.class, args); } @GetMapping("/test") public String test(String name){ System.out.println("进入方法"); int var=100; System.out.println(name); System.out.println(var); System.out.println("方法结束"); return "OK"; } }
项目启动配置参考,需要启用Debug配置
(2)自定义调试器代码
开发调试器需要JNI工具支持,JDI操作的API工具在tools.jar中 ,需要在 CLASSPATH 中添加/lib/tools.jar
import com.sun.jdi.*; import com.sun.jdi.connect.AttachingConnector; import com.sun.jdi.connect.Connector; import com.sun.jdi.event.*; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; import com.sun.tools.jdi.SocketAttachingConnector; import java.util.List; import java.util.Map; /** * 通过JNI工具测试Debug * @author zhangyu * @date 2022/2/20 */ public class TestDebugVirtualMachine { private static VirtualMachine vm; public static void main(String[] args) throws Exception { //获取SocketAttachingConnector,连接其它JVM称之为附加(attach)操作 VirtualMachineManager vmm = Bootstrap.virtualMachineManager(); List<AttachingConnector> connectors = vmm.attachingConnectors(); SocketAttachingConnector sac = null; for(AttachingConnector ac : connectors) { if(ac instanceof SocketAttachingConnector) { sac = (SocketAttachingConnector) ac; } } assert sac != null; //设置好主机地址,端口信息 Map<String, Connector.Argument> arguments = sac.defaultArguments(); Connector.Argument hostArg = arguments.get("hostname"); Connector.Argument portArg = arguments.get("port"); hostArg.setValue("127.0.0.1"); portArg.setValue(String.valueOf(8800)); //进行连接 vm = sac.attach(arguments); //相应的请求调用通过requestManager来完成 EventRequestManager eventRequestManager = vm.eventRequestManager(); //创建一个代码判断,因此需要获取相应的类,以及具体的断点位置,即相应的代码行。 ClassType clazz = (ClassType) vm.classesByName("com.zy.debugger.DebuggerApplication").get(0); //设置断点代码位置 Location location = clazz.locationsOfLine(22).get(0); //创建新断点并设置阻塞模式为线程阻塞,即只有当前线程被阻塞 BreakpointRequest breakpointRequest = eventRequestManager.createBreakpointRequest(location); //设置阻塞并启动 breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); breakpointRequest.enable(); //获取vm的事件队列 EventQueue eventQueue = vm.eventQueue(); while(true) { //不断地读取事件并处理断点记录事件 EventSet eventSet = eventQueue.remove(); EventIterator eventIterator = eventSet.eventIterator(); while(eventIterator.hasNext()) { Event event = eventIterator.next(); execute(event); } //将相应线程resume,表示继续运行 eventSet.resume(); } } /** * 处理监听到事件 * @author zhangyu * @date 2022/2/20 */ public static void execute(Event event) throws Exception { //获取的event为一个抽象的事件记录,可以通过类型判断转型为具体的事件,这里我们转型为BreakpointEvent,即断点记录, BreakpointEvent breakpointEvent = (BreakpointEvent) event; //并通过断点处的线程拿到线程帧,进而获取相应的变量信息,并打印记录。 ThreadReference threadReference = breakpointEvent.thread(); StackFrame stackFrame = threadReference.frame(0); List<LocalVariable> localVariables = stackFrame.visibleVariables(); //输出当前线程栈帧保存的变量数据 localVariables.forEach(t -> { Value value = stackFrame.getValue(t); System.out.println("local->" + value.type() + "," + value.getClass() + "," + value); }); } }
(3)代码分析
【1】通过Bootstrap.virtualMachineManager();获取连接器,客户端即通过相应的connector进行连接,配置服务器程序ip地址和端口,连接后获取对应服务器的VM信息。
定位目标debug的类文件,可通过遍历获取的类集合并结合VirtualMachine获取类信息实现
【3】对目标类代码特定位置设置并启用断点
【4】记录断点信息,阻塞服务器线程,并根据对应事件获取相应的信息
【5】执行event.resume释放断点,服务器程序继续运行
(4)运行测试
启动 SpringBoot 的 web 项目,也就是服务器程序。启动调试器代码时使用debug模式,并在该位置查看所获取的信息,同时避免直接释放断点。
【2】DebuggerApplication クラスの 22 行目にブレークポイントの位置を設定します。
【3】起動後にインターフェースをテストします。サーバー プログラム コンソールに次の結果が出力されることがわかります。 22行目はまだ実行されていません。
【4】現時点ではデバッガプログラムを監視しています。サーバープログラムのスタックフレームのデータが取得できていることが確認できます
#[5] ブレークポイントを解除するとサーバーは正常に動作し、リクエスト処理プロセスが完了します。 ##############################
以上がJavaプラットフォームデバッグシステムの原理は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。