往往很多情况本地调试和真实线上调试测试的结果是不一致的,真实调用线上的执行环境最能体现实际情况,配置java的远程调试功能可以现实这一需求。当一般线上环境的申请步骤往往非常复杂,包括权限申请,风险评估一系列的步骤,周期往往更长。
Java 远程调试的核心是 Java 平台调试器体系结构(Java Platform Debugger Architecture, JPDA)。JPDA 由三个主要部分组成:
调试的步骤:
需要注意的是,用于远程debug的代码必须与远程部署的代码完全一致,不能发生任何的修改,否则打上的断点将无法命中
服务端
bash自动换行:关放大阅读展开代码java -Denv=dev -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9999 ./blogactions.jar
参数说明:
不同的jdk版本参数大致相同,可以参考: Connection and Invocation Details
当出现 Listening for transport dt_socket at address:字样表示服务端调试服务启动成功
bash自动换行:关放大阅读展开代码[hedeoer@centos79 test]$ java -Denv=dev -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9999 ./blogactions.jar Listening for transport dt_socket at address: 9999
客户端调试工具,此处以Intellij IDEA为例
Transport(通信方式):有Socket和shared memory两种,最常用、最标准的调试通信方式。IntelliJ IDEA 和被调试的 Java 应用程序会通过网络套接字 (TCP/IP) 进行通信;其次共享内存是一种进程间通信 (Inter-Process Communication, IPC) 的方式,因此调试器和被调试的应用程序必须运行在同一台物理机器上。。
allow multiple instances :当勾选时,则当你使用这个配置点击 "Run" 或 "Debug" 按钮启动一个实例后,这个实例会开始运行。此时,如果你再次点击 "Run" 或 "Debug" 按钮,IntelliJ IDEA 不会停止第一个实例,而是会直接启动一个全新的、独立的第二个实例。
开启调试前,需要设置需要调试断点:
出现idea出现如下字样表示调试链接成功:
工具定位:
plaintext自动换行:关放大阅读展开代码安装直接下载对应文件jar或者系统安装包(deb,rpm等)即可
启动
bash自动换行:关放大阅读展开代码java -jar ./arthas-boot.jar
arthas 4.0.5文件目录说明
bash自动换行:关放大阅读展开代码. # Arthas 的根目录 ├── arthas-agent.jar # Arthas的Java Agent实现,负责注入目标JVM并进行字节码增强。 ├── arthas-bin.zip # 你下载的Arthas完整二进制发行版的压缩包。 ├── arthas-boot.jar # Arthas的启动器,通过 `java -jar arthas-boot.jar` 运行,用于发现并附加(attach)到目标Java进程。 ├── arthas-client.jar # Arthas的命令行客户端,提供与用户交互的界面。 ├── arthas-core.jar # Arthas的核心实现库,包含了所有诊断命令的逻辑。 ├── arthas.properties # Arthas的配置文件,用于修改默认设置。 ├── arthas-spy.jar # "间谍"jar包,用于解决目标应用和Arthas之间的ClassLoader隔离问题。 ├── as.bat # Windows系统下的便捷启动脚本。 ├── as-service.bat # Windows系统下将Arthas作为服务运行的脚本。 ├── as.sh # Linux/macOS系统下的便捷启动脚本。 ├── async-profiler/ # 存放async-profiler工具的目录,为`profiler`火焰图命令提供底层支持。 │ ├── libasyncProfiler-linux-arm64.so # 适用于Linux ARM64架构的原生探查库。 │ ├── libasyncProfiler-linux-x64.so # 适用于Linux x86_64架构的原生探查库。 │ └── libasyncProfiler-mac.dylib # 适用于macOS的原生探查库。 ├── install-local.sh # 在本地安装Arthas的脚本,方便在任何路径下启动。 ├── lib/ # 存放Arthas自身所需的JNI(Java Native Interface)原生库的目录。 │ ├── libArthasJniLibrary-aarch64.so # 适用于Linux ARM64架构的Arthas原生库。 │ ├── libArthasJniLibrary.dylib # 适用于macOS的Arthas原生库。 │ ├── libArthasJniLibrary-x64.dll # 适用于Windows x86_64架构的Arthas原生库。 │ └── libArthasJniLibrary-x64.so # 适用于Linux x86_64架构的Arthas原生库。 ├── logback.xml # 日志框架Logback的配置文件,控制Arthas自身的日志输出。 └── math-game.jar # 一个用于演示和学习的Java示例程序,方便用户快速上手练习Arthas命令。
搭配 Itellij idea使用,需要安装插件arthas idea
安装该插件后,只要保持本地代码和服务器运行代码一致,通过该插件生成arthas相关命令,可以快速调试线上环境。比如:
bash自动换行:关放大阅读展开代码trace -E cn.hedeoer.actions.handler.RepeatCommitHandler handleRepeatCommit -n 5 --skipJDKMethod false '1==1'
bash自动换行:关放大阅读展开代码dashboard
可以查看java 线程级别各个线程的运行状态,占用的cpu情况;内存占用情况,以及整体jvm信息
bash自动换行:关放大阅读展开代码thread -n 3
找出最忙的前3个线程:
bash自动换行:关放大阅读展开代码[arthas@15017]$ thread -n 3 "arthas-command-execute" Id=97 cpuUsage=0.12% deltaTime=0ms time=39ms RUNNABLE at java.management@11/sun.management.ThreadImpl.dumpThreads0(Native Method) at java.management@11/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466) at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:206) at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122) at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82) at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18) at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111) at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108) at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385) at java.base@11/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) at java.base@11/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base@11/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) at java.base@11/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base@11/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base@11/java.lang.Thread.run(Thread.java:834)
查看发生死锁二线程:
bash自动换行:关放大阅读展开代码[arthas@15017]$ thread -b No most blocking thread found!
对比线上环境的代码是否和本地的同步了
比如查看类RepeatCommitHandler中 handleRepeatCommit 方法的源代码
bash自动换行:关放大阅读展开代码jad --source-only cn.hedeoer.actions.handler.RepeatCommitHandler handleRepeatCommit
实际方法
java自动换行:关放大阅读展开代码@Override public List<CommitFile> queryExistsFailSyncCommitFile(String commitHash) { return SqlSessionHolder.getVanblogSqlSession() .getMapper(CommitFileMapper.class) .queryExistsFailSyncCommitFile(commitHash); }
命令查看:
监视 CommitFileServiceImpl 类中的 queryExistsFailSyncCommitFile 方法,当它被调用时,打印出它的入参、返回值和抛出的异常。这个监视只执行 5 次,并且在打印对象时,最多展开 3 层深度。
bash自动换行:关放大阅读展开代码watch cn.hedeoer.actions.service.CommitFileServiceImpl queryExistsFailSyncCommitFile '{params,returnObj,throwExp}' -n 5 -x 3
bash自动换行:关放大阅读展开代码Affect(class count: 1 , method count: 1) cost in 104 ms, listenerId: 4 method=cn.hedeoer.actions.service.CommitFileServiceImpl.queryExistsFailSyncCommitFile location=AtExit ts=2025-08-28 18:06:17.445; [cost=2.661136ms] result=@ArrayList[ @Object[][ @String[93ce6a00ccdd0d77c40eb870c82c0d9f349a08d3], ], @ArrayList[isEmpty=true;size=0], null, ] method=cn.hedeoer.actions.service.CommitFileServiceImpl.queryExistsFailSyncCommitFile location=AtExit ts=2025-08-28 18:07:13.037; [cost=2.274507ms] result=@ArrayList[ @Object[][ @String[93ce6a00ccdd0d77c40eb870c82c0d9f349a08d3], ], @ArrayList[isEmpty=true;size=0], null, ]
java自动换行:关放大阅读展开代码@Override public Integer addUserRequestLog(UserRequestLog userRequestLog) { SqlSession session = SqlSessionHolder.getVanblogSqlSession(); return session.getMapper(UserRequestLogMapper.class).insertUserRequestLog(userRequestLog); }
追踪 UserRequestLogServiceImpl 类中 addUserRequestLog 方法的内部调用链路。追踪 5 次方法执行,并且在追踪过程中不要跳过 JDK 核心库里的方法调用。
bash自动换行:关放大阅读展开代码trace cn.hedeoer.actions.service.UserRequestLogServiceImpl addUserRequestLog -n 5 --skipJDKMethod false
bash自动换行:关放大阅读展开代码Affect(class count: 1 , method count: 1) cost in 75 ms, listenerId: 5 `---ts=2025-08-28 18:12:02.671;thread_name=JettyServerThreadPool-42;id=42;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29 `---[6.525456ms] cn.hedeoer.actions.service.UserRequestLogServiceImpl:addUserRequestLog() +---[33.54% 2.188728ms ] cn.hedeoer.actions.utils.SqlSessionHolder:getVanblogSqlSession() #11 +---[0.99% 0.064879ms ] org.apache.ibatis.session.SqlSession:getMapper() #12 `---[63.52% 4.144776ms ] cn.hedeoer.actions.mapper.UserRequestLogMapper:insertUserRequestLog() #12
捕获 SqlSessionHolder 类中的 clear 方法被调用时的线程执行堆栈。这个动作会执行 5 次,每次方法被调用时,都会打印出当时的完整调用链路。
bash自动换行:关放大阅读展开代码stack cn.hedeoer.actions.utils.SqlSessionHolder clear -n 5
可以看出SqlSessionHolder类的clear方法在2025-08-28 18:17
RepeatCommitHandler类的handleRepeatCommit方法调用了1次
bash自动换行:关放大阅读展开代码ts=2025-08-28 18:17:59.363;thread_name=JettyServerThreadPool-36;id=36;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29 @cn.hedeoer.actions.utils.SqlSessionHolder.clear() at cn.hedeoer.actions.handler.RepeatCommitHandler.handleRepeatCommit(RepeatCommitHandler.java:134) at cn.hedeoer.actions.BlogGitCommitApplication.lambda$main$0(BlogGitCommitApplication.java:102) at io.javalin.http.JavalinServlet$lifecycle$1$1$1.invoke(JavalinServlet.kt:38) at io.javalin.http.JavalinServlet$lifecycle$1$1$1.invoke(JavalinServlet.kt:38) at io.javalin.http.JavalinServletHandler.executeNextTask(JavalinServletHandler.kt:99) at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$lambda-1(JavalinServletHandler.kt:85) 其他内容省略。。。
为 CommitFileService 接口的 queryExistsFailSyncCommitFile 方法设置一个‘时光隧道’,录制下前 5 次该方法的调用现场。录制完成后,你可以随时回溯(查看)甚至重放(replay)某一次具体的调用
bash自动换行:关放大阅读展开代码tt -t cn.hedeoer.actions.service.CommitFileService queryExistsFailSyncCommitFile -n 5
实验触发3次:
回放第二次,即编号为1001的
bash自动换行:关放大阅读展开代码tt -p -i 1001
结果
表明回放方法正确返回(IS-RETURN true),没有异常(IS-EXCEPTION false),耗时3.459221毫秒,返回值为空列表。
本文作者:hedeoer
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!