
JDK 命令行调试器 JDB 详解从会话启动、断点单步、异常捕获到虚拟线程调试【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdkJDBJava Debugger是 JDK 内置的命令行调试器本文以官方手册页src/jdk.jdi/share/man/jdb.md为核心完整覆盖jdb命令的启动方式、命令行选项、断点/单步/异常调试用法并结合jdk.jdi模块的参考实现源码如 TTY.java、SunCommandLineLauncher.java与 JDWP 调试代理 debugInit.c讲清每个选项背后的连接机制与默认行为。读完后你可以独立完成 JDB 的本地启动与远程 attach 调试熟练使用断点、单步、catch/ignore异常调试并正确配置虚拟线程Virtual Threads的跟踪策略。JDB 与 Java 平台调试架构JPDAJDB 是一个简单的 Java 类命令行调试器。jdb命令及其选项直接调用 JDB它同时是 Java 平台调试架构Java Platform Debugger Architecture, JPDA的演示实现可用于检查与调试本地或远程 JVM。命令的基本形式Synopsis为jdb [options] [classname] [arguments]optionsjdb命令行选项见下文“jdb 命令选项”一节classname要调试的主类名arguments传给该类main()方法的参数。从源码结构看JDB 的前端是一个示例包实现类位于 TTY.java它通过com.sun.jdi包虚拟机的 Java 镜像 API与目标 JVM 通信而连接器的具体实现在com.sun.tools.jdi包中。两者共同构成“调试器前端 连接器/传输层 目标 JVM 中的 Debug AgentJDWP”三段式结构。启动一个 JDB 会话启动 JDB 会话的方式有多种。最常用的是让 JDB 直接启动一个新的 JVM 来运行待调试应用的主类——只需在命令行中用jdb替换java即可。例如若应用主类是MyClass则jdb MyClass这样启动时jdb会以给定参数调用第二个 JVM即被调试进程debuggee加载指定类并在执行该类第一条指令之前停下。另一种方式是把jdbattach 到已在运行的 JVM 上。启动一个可被 attach 的 JVM 的写法是该参数会加载进程内调试库并指定连接类型java -agentlib:jdwptransportdt_socket,servery,suspendn MyClass随后用下面的命令把jdbattach 上去jdb -attach 8000这里8000是正在运行的 JVM 的地址。这种情况下jdb命令行里不再指定MyClass因为jdb是连接到一个已存在的 JVM而不是启动新 JVM。JPDA 还支持许多其他将调试器连接到 JVM 的方式jdb全部支持。下面结合源码说明这些选项是如何被解析和实现的。源码视角命令行选项的解析与连接建立TTY.java 的main()方法遍历argv把选项分为几类处理连接类选项-attach、-listen、-listenany、-connect它们构造一个 connectSpec 字符串例如com.sun.jdi.SocketAttach:port8000 com.sun.jdi.SharedMemoryListen:name... com.sun.jdi.CommandLineLaunch:mainMyClass,...值得注意的是-attach的“默认连接机制”并非固定为 socket源码 TTY.java 中-attach会先调用supportsSharedMemory()探测dt_shmem传输是否可用TTY.java 通过遍历allConnectors()检查是否存在名为dt_shmem的传输。若可用则生成com.sun.jdi.SharedMemoryAttach:name地址否则回退为com.sun.jdi.SocketAttach:port端口host:port形式会被拆分为hostname...,port...见 TTY.java 的addressToSocketArgs。这与手册“-attach使用默认连接机制”的表述一致也解释了跨平台差异Windows 等平台上共享内存传输可用时-attach pid走的是共享内存而非 socket 端口。转发给被调试进程debuggee的选项-verbose[:class|gc|jni]、-D、-X及旧式选项-verify、-verbosegc会被累加进javaArgsTTY.java-R选项则去掉前缀后同样累加TTY.java-classpath会连同其取值一起转发-tclient/-tserver分别以-client/-server置于javaArgs最前TTY.java。其余非选项 token从当前位置起全部拼成被调试程序的“主类 参数”命令行遇到空格或逗号时会重新加引号addArgumentTTY.java因此jdb MyClass a,b、jdb MyClass a b的参数切分行为与注释中列出的示例一致TTY.java。当命令行未显式给出连接方式时默认连接串就是启动型连接器com.sun.jdi.CommandLineLaunch:若同时提供了主类则追加为mainMyClass,...TTY.java。注意源码中的约束指定了非CommandLineLaunch连接器时不能再附带主类或目标 VM 参数否则会报usageError。启动型连接器如何拉起被调试 JVMjdb MyClass背后实际执行的是 SunCommandLineLauncher.java 的launch()方法。其流程为先在本地启动一个监听socket 或共享内存两种传输SunCommandLineLauncher.java优先尝试SharedMemoryTransportService失败则退化为SocketTransportService组装 JDWP 参数并拼装最终命令java.home/bin/java options -Xrunjdwp:transportdt_shmem|dt_socket,addressaddr,suspendy|n,includevirtualthreadsy|n main class and args对应源码见 SunCommandLineLauncher.java。其中suspend默认取自连接器参数suspend默认y即 SunCommandLineLauncher.java 中默认值为true的布尔参数这正是手册所说“加载指定类、在执行第一条指令前停下”的实现来源includevirtualthreads默认值为nSunCommandLineLauncher.java其含义见后文“虚拟线程”一节。断点BreakpointsJDB 中可以在行号或方法首条指令处设置断点例如stop at MyClass:22在包含MyClass的源文件第 22 行的首条指令处设置断点stop in java.lang.String.length在java.lang.String.length方法开头设置断点stop in MyClass.clinit用clinit指定MyClass的静态初始化代码。当方法重载时必须给出参数类型以便选择正确的断点目标方法。例如MyClass.myMethod(int,java.lang.String)或MyClass.myMethod()。clear命令删除断点语法为clear MyClass:45。不带参数的clear或stop会列出当前已设置的所有断点。cont命令继续执行。这些命令在 Commands.java 中分别实现为commandStop、commandClear、commandCont等由 TTY.java 的命令分发逻辑路由。底层则通过com.sun.jdi.request包的BreakpointRequest、MethodEntryRequest等请求对象注册到目标 VM 的事件请求管理器事件到达时breakpointEvent()打印命中位置并按挂起策略交出控制权见 TTY.java。单步Steppingstep命令把执行推进到下一行无论该行在当前栈帧还是被调用的方法中next命令把执行推进到当前栈帧的下一行。源码中step/next与cont一样属于“会先打印提示符再异步执行”的命令TTY.java。另外JDB 支持repeat机制开启后repeat on直接回车可重复执行up、down、step、stepi、next、cont、list、pop、reenter等可重复命令TTY.java连续单步时会比连续敲step省力不少。异常调试Exceptions当抛出一个异常而抛出线程的整个调用栈中都没有对应的catch语句时JVM 通常会打印异常跟踪并退出。但在 JDB 下运行控制权会在异常实际抛出处交还给 JDB此时可用jdb命令诊断异常的成因。catch命令让被调试应用在其他被抛出的异常处停下例如catch java.io.FileNotFoundException或catch mypackage.BigTroubleException。凡是属于指定类或其子类的实例都会使应用在该异常被抛出的位置停下。ignore命令取消先前某个catch命令的效果。注意ignore只是让“调试器”忽略这些异常并不会让被调试的 JVM 本身去忽略特定异常。事件呈现逻辑可以在 TTY.java 的exceptionEvent()中看到若catchLocation为空则提示Exception occurred uncaught未被捕获否则提示Exception occurred caught并附带捕获位置。jdb 命令选项当用jdb替代java时jdb接受与java命令相同的一批选项。jdb命令接受的选项如下选项说明-help显示帮助信息。-sourcepath dir1:dir2:...在给定路径中搜索源文件未指定时默认路径为.。-attach address以默认连接机制把调试器 attach 到正在运行的 JVM。-listen address用标准监听连接器等待正在运行的 JVM 连接到指定地址。-listenany用标准监听连接器在任意可用地址上等待正在运行的 JVM 连接。-launch在jdb启动后立即启动被调试应用省去run命令。被调试应用启动后会在初始应用类加载前停下此时可设置所需断点并用cont继续。-listconnectors列出本 JVM 中可用的连接器。-connect connector-name:name1value1....用命名连接器和给定参数值连接目标 JVM。-dbgtrace [flags]打印用于调试jdb自身的信息。-tclient在 Java HotSpot VM client 中运行应用。-trackallthreads跟踪所有线程的创建包括虚拟线程。见下文“调试虚拟线程”。-tserver在 Java HotSpot VM server 中运行应用。-Joption把 option 传给 JDB 自己的 JVMoption 为java参考页中描述的一个选项例如-J-Xms48m将启动内存设为 48 MB。-Roption把 option 传给被调试debuggeeJVM例如-R-Xms48m。-v或-verbose[:class\|gc\|jni]开启 verbose 模式转发给被调试进程。-Dnamevalue设置系统属性转发给被调试进程。-classpath dir列出以冒号分隔、用于查找类的目录转发给被调试进程。-X option非标准的目标 JVM 选项转发给被调试进程。此外JDB 还支持其他选项以提供把调试器连接到待调试 JVM 的替代机制即-connect/-attach/-listen一类。源码层面几个值得留意的细节-help并非唯一帮助入口-?、-h、--help均可TTY.java-version会直接打印版本并退出。-dbgtrace不带参数或参数非法时置为TRACE_ALL否则把参数当作整型标志位解码TTY.java。-sourcepath在 JDB 内部生效Env.setSourcePath用于list等命令显示源码而-classpath会原样转发给被调试进程TTY.java。历史选项-tclassic已被移除遇到会直接报错退出TTY.java——这是一个与早期文档差异点当前仓库版本中 Classic VM 不再受支持。调试虚拟线程Working With Virtual Threads虚拟线程常常以极大的数量和创建频率出现足以压垮调试器。因此JDB 默认不跟踪虚拟线程的创建只跟踪那些曾向调试器送达事件如断点事件的虚拟线程。使用-trackallthreads选项可以让 JDB 在虚拟线程创建时就全部跟踪。JDB 首次连接时会向 Debug Agent 请求所有已知线程的列表。出于同样的“列表过大可能压垮调试器”的考虑Debug Agent 默认不会在列表中返回任何虚拟线程。Debug Agent 提供includevirtualthreads选项来改变这一行为使列表包含所有已知虚拟线程。JDB 的-trackallthreads选项在JDB 启动launch应用进行调试时会自动开启 Debug Agent 的includevirtualthreads。但要注意对于 JDB attach 上去之前就已创建的虚拟线程Debug Agent 可能并不知情。这条链路在源码中一目了然命令行解析-trackallthreads置位静态标志trackVthreadsTTY.java构造连接串若使用默认CommandLineLaunch连接器且该标志为真则向 connectSpec 追加includevirtualthreadsyTTY.java连接器参数默认值includevirtualthreads参数由 SunCommandLineLauncher.java 注册默认值n并在拼装 JDWP 命令行时以,includevirtualthreadsy|n传给目标 JVMSunCommandLineLauncher.java目标端解析JDWP 调试代理 debugInit.c 帮助文本写明 “List of all threads includes virtual threads as well as platform threads”并在选项解析处消费该键debugInit.c。会话中的实用技巧来自参考实现手册主体之外阅读 TTY.java 还能获得若干提高调试效率的用法均为当前仓库源码可确认的行为启动命令文件JDB 启动时模仿 gdb 的 init 文件机制依次尝试读取$HOME/jdb.ini或.jdbrc再尝试./jdb.ini或./.jdbrc若两个规范路径相同则只读一次TTY.java。可在其中预置stop in ...、sourcepath ...等命令。会话中也可用read file命令执行命令文件TTY.java。!!重执行与数字前缀重复输入!!会复用上一条命令命令前加数字可重复执行如5 stepTTY.java。monitor 命令monitor cmd注册的命令会在每次 VM 挂起时自动执行TTY.java适合挂接周期性查看threads、where之类的检查。完整命令表TTY.java 的commandList给出全部可识别命令catch、clear、cont、eval、fields、ignore、interrupt、kill、list、locals、load、monitor、next、print、step、stepi、stop、suspend、resume、threads、trace、where、watch等并标注了哪些命令在“未连接/只读连接”状态下可用。小结与延伸阅读主题文档/源码位置jdb手册页本文主体src/jdk.jdi/share/man/jdb.mdJDB 前端实现选项解析、命令分发、init 文件src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTY.java各调试命令的具体实现src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/Commands.java启动型连接器拉起 debuggee、组装-Xrunjdwp参数src/jdk.jdi/share/classes/com/sun/tools/jdi/SunCommandLineLauncher.java目标端 JDWP 调试代理includevirtualthreads等选项解析src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c要点回顾用jdb MyClass直接启动调试、用jdb -attach 地址挂到已开启jdwp代理的 JVMstop at/stop in含clinit与重载签名clearcont完成断点管理step/next控制单步粒度catch/ignore管理异常停点只作用于调试器不改变 JVM 行为-J作用于 JDB 自身、-R作用于被调试进程、其余如-D/-classpath/-verbose转发给被调试进程虚拟线程默认不跟踪-trackallthreads在 JDB 以 launch 方式启动应用时会自动为 Debug Agent 打开includevirtualthreads但 attach 场景下对已存在的虚拟线程不可见。【免费下载链接】jdkJDK main-line development https://openjdk.org/projects/jdk项目地址: https://gitcode.com/GitHub_Trending/jd/jdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考