JPDA(Java Platform Debugger Architecture)는 Java Platform Debugger Architecture의 약자로 JPDA에서 제공하는 API를 통해 개발자가 쉽고 유연하게 Java 디버깅 애플리케이션을 구축할 수 있습니다. .
JPDA는 주로 JVMTI(Java Virtual Machine Tool Interface), JDWP(Java Debug Wire Protocol) 및 JDI(Java Debug Interface)의 세 부분으로 구성됩니다.
Java 프로그램은 모두 Java 가상 머신에서 실행됩니다. Java 프로그램을 디버그하려면 실제로 Java 가상 머신에서 현재 실행 상태를 요청하고, 가상 머신에 특정 명령을 내리고, 일부 콜백을 설정해야 합니다. 그러면 Java의 디버깅 시스템은 가상 머신 디버깅을 위한 완전한 도구 및 인터페이스 세트입니다.
1: 편집기가 클라이언트 및 서버 프로그램 역할을 하여 노출된 청취 포트를 통해 소켓 연결을 설정합니다.
2: IDE 클라이언트가 중단점 위치를 생성하고 JDI 인터페이스를 통한 중단점 이벤트 서버(프로그램)의 VM에 주어지면 VM은 suspension을 호출하여 VM을 일시 중지합니다
3: VM이 일시 중지된 후 클라이언트가 얻어야 하는 VM 정보가 클라이언트에 반환됩니다. 반환 후 VM 재개는 실행 상태를 재개합니다
4 : 클라이언트가 VM에서 반환된 정보를 얻은 후 다른 방식으로 표시할 수 있습니다
JPDA는 완전하고 독립적인 시스템을 정의합니다. 상대적으로 독립적인 세 개의 레이어로 구성되며 세 레이어 간의 상호 작용 모드를 규정하거나 통신을 위한 인터페이스를 정의합니다.
낮은 것부터 높은 것까지의 세 가지 수준은 JVMTI(Java Virtual Machine Tool Interface), JDWP(Java Debug Wire Protocol) 및 JDI(Java Debug Interface)입니다.
이 세 가지 모듈은 디버깅 프로세스를 디버거(디버거), 디버기(디버기) 및 이들 간의 커뮤니케이터라는 여러 가지 자연스러운 개념으로 분류합니다.
디버기는 우리가 디버깅하려는 Java 가상 머신에서 실행되며, JVMTI 표준 인터페이스를 통해 현재 가상 머신의 정보를 모니터링할 수 있습니다. 디버거는 이러한 인터페이스를 통해 사용자가 사용할 수 있는 디버깅 인터페이스를 정의합니다. 디버깅된 가상 머신은 디버깅 명령을 보내고 디버거는 디버깅 결과를 수락하고 표시합니다.
JDWP 통신 프로토콜은 디버깅 명령과 디버깅 결과를 전송하는 데 사용됩니다. 디버깅 중에 디버거와 디버기를 연결합니다. 모든 명령은 JDWP 명령 패키지에 캡슐화되어 전송 계층을 통해 디버기에게 전송됩니다. 디버기는 JDWP 명령 패키지를 수신한 후 명령을 구문 분석하고 이를 디버기에서 실행되는 JVMTI 호출로 변환합니다.
유사한 상황은 JVMTI가 실행 결과를 JDWP 데이터 패킷 형식으로 변환하고 결과를 디버거로 보낸 다음 다시 JDI 호출로 전달하는 것입니다. 디버거 개발자는 JDI를 통해 데이터를 얻고 지침을 발행합니다.
위 그림에 표시된 것처럼 JPDA는 세 가지 레이어로 구성됩니다.
JVM TI
- Java VM 도구 인터페이스. VM에서 제공하는 디버깅 서비스를 정의합니다. JVM TI
- Java VM 工具接口。定义 VM 提供的调试服务。
JDWP
- Java 调试通信协议。定义被调试者和调试器进程之间的通信。
JDI
JDWP
- Java 디버깅 통신 프로토콜입니다. 디버기와 디버거 프로세스 간의 통신을 정의합니다.
JDI
- Java 디버깅 인터페이스. 도구 개발자가 원격 디버거 애플리케이션을 작성하는 데 쉽게 사용할 수 있는 고급 Java 언어 인터페이스를 정의합니다.
JPDA 인터페이스를 통해 자체 디버깅 도구를 개발할 수 있습니다. 이러한 JPDA에서 제공하는 인터페이스와 프로토콜을 통해 디버거 개발자는 특정 개발자의 요구 사항에 따라 Java 디버깅 애플리케이션을 확장하고 사용자 정의할 수 있습니다. 앞서 언급한 IDE 디버깅 도구는 모두 JPDA 시스템을 기반으로 개발되었으며, 유일한 차이점은 서로 다른 그래픽 인터페이스를 제공하고 일부 다른 사용자 정의 기능을 가질 수 있다는 것입니다. 또한 JPDA는 표준 집합이며 모든 JDK 구현은 이 표준을 충족해야 합니다. 따라서 JPDA를 통해 개발된 디버깅 도구는 본질적으로 크로스 플랫폼이며 가상 머신 구현과 독립적이며 JDK 버전 독립적입니다. 이식의 장점이 있으므로 대부분의 디버깅 도구는 이 시스템을 기반으로 합니다. 2. 원격 디버깅 예제【1】SpringBoot WEB 프로젝트를 빌드합니다. 현재 사용하고 있는 SpringBoot 버전은 2.3.0.RELEASE입니다. 해당 Tomcat 버전은 9.X입니다. SpringBoot 프로젝트를 패키징하고 애플리케이션 포트를 9999로 설정합니다. JAR 패키지를 사용하든 Docker를 사용하든 이 프로그램을 Linux 서버에 배포하는 것은 원격 디버깅과 관련이 없습니다. 【3】배포 프로그램의 코드 참조는 다음과 같으며 이는 인쇄 정보를 처리하는 간단한 요청입니다
/** * 测试程序 * @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에 배포된 일반 웹 프로젝트라면 다음을 참고하세요. 🎜🎜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 중국어 웹사이트의 기타 관련 기사를 참조하세요!