java
标准化、规范化
java代码风格
【目录】java
编程-java8
编程-java9
编程-java-见过的异常
gradle
编程-java21
编程-java23
编程-java17
编程-java11
【目录】java-web-其它框架
java-vertx
quarkus
javalin
solon
Helidon
【目录】spring家族
spring
springcloud + nacos
consul
springboot启动流程
springboot使用及原理
springcloud
优化springboot
【java高级】
java-多线程-问题记录
java高级-ArrayList
java高级-HashMap
jdk源码解析-TreeMap红黑树
java对象占用多少字节
juc(并发)
ThreadPoolExecutor中ctl变量的理解
ThreadPoolExecutor分析
JVM(java虚拟机)
jvm学习路线
jvm
Java启动参数
debug
java-debug-arthas
java-debug-jdb
高并发/高性能/高可用
设计代码或编写代码时应该考虑的
如何发现系统中的瓶颈?
场景分析
mysql
mysql explain
mysql主从
mysql常见异常
方法论
工作中遇到的问题记录
代码优化
学习的思路
产品
本文档使用 MrDoc 发布
-
+
首页
java-debug-jdb
# 生产环境使用jdb命令行添加断点调试java应用 关键词:调试 jdb 命令行加断点 命令行调试java应用 生产环境添加断点 ## 原因/背景 生产环境有段旧代码如下: ```text public String saveMapping(JSONObject jsonObject) { try { String mapping = jsonObject.getJSONArray("mapping").toJSONString(); Long id = jsonObject.getLong("id"); repository.saveMapping(mapping, id); return "保存mapping配置成功"; } catch (Exception e) { throw new ServiceException("保存mapping配置失败"); } } ``` 该代码有时正常有时异常,但没有把异常变量打印到日志中,导致排查问题不方便,也就有了在生产环境加断点debug的需求。(最后发现e是org.springframework.dao.DataIntegrityViolationException "Value too long for column",相信小伙伴已经知道怎么处理了) 目前的[arthas](https://arthas.aliyun.com/en-us/)(当前最新版本:3.5.5)很强大,但不支持断点调试,所以只有使用java内置的jdb. ## 原理 -Xrunjdwp:JVM使用(java debug wire protocol)来运行调试环境; -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=xxx dt_socket:使用的通信方式 server:是主动连接调试器还是作为服务器等待调试器连接 suspend:是否在启动JVM时就暂停,并等待调试器连接 address:地址和端口,地址可以省略,两者用冒号分隔 根据文档和stackoverflow上的讨论,JVM 1.5以后的版本应该使用类似下面的命令(老的还是可以使用的): `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=xxx` ## 步骤 ### 服务端 在服务上执行一个web应用,命令行如下: `java -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar lab-jdb-remote-debug-0.0.1-SNAPSHOT.jar` ### 客户端 注意:客户端和服务端可以是同一台机器。 #### 连接 `jdb -connect com.sun.jdi.SocketAttach:hostname=IP,port=5005` 或 `jdb -attach IP:5005` #### 调试 1. 添加方法断点/清除方法断点 用法: `stop in ${classFullName}.${methodName}` 如:`stop in cn.valuetodays.lab.IndexService.saveUser` 清除:`clear ${classFullName}.${methodName}` 2. 添加行断点/清除行断点 用法: `stop at ${classFullName}:${lineNumber}` 如:`stop at cn.valuetodays.lab.IndexService:19` 清除:`clear ${classFullName}:${lineNumber}` 3. 下一步 `next` 4. 继续执行,若有断点则跳到下一个断点 `cont` 5. 打印栈桢中的局部变量表(用到了jvm知识) `locals` 6. 打印变量的内容 `print xxx` 7. 列出所有断点 `clear` (对,没错,就是列出所有断点,尽管命令的名字是clear) 8. 退出 `exit` 回到第一部分的“原因”处 - 确认生产环境代码的tag - 确认该类的该方法所在的位置及“throw new ServiceException("保存mapping配置失败");”的行号 - 使用行断点 - 前端再次请求 - 若能进入到断点,就print e即可看到内容。 ## 实操 ## 代码 ```sql create table if not exists USER ( id int not null primary key auto_increment, name varchar(16) ); ``` ```java package cn.valuetodays.lab; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service public class IndexService { @Autowired private JdbcTemplate jdbcTemplate; public String saveUser(String name) { String sql = "insert into user(name) values(?)"; try { int update = jdbcTemplate.update(sql, name); return "suc: " + update; } catch (Exception e) { return "fail"; } } } ``` ### 实操步骤 ```text ## 以两个##开头的是说明 ## 先连接到服务端,我是使用同一台机器操作的 [root@server201 ~]# jdb -connect com.sun.jdi.SocketAttach:hostname=server201,port=5005 Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable Initializing jdb ... ## 在方法上加断点 > stop in cn.valuetodays.lab.IndexService.saveUser Set breakpoint cn.valuetodays.lab.IndexService.saveUser ## 查看所有断点 > clear Breakpoints set: breakpoint cn.valuetodays.lab.IndexService.saveUser ## 此时触发请求(curl -XGET http://server201:50001/saveUser?name=a1111111b3333333333333 ),会在断点外停下 > Breakpoint hit: "thread=http-nio-50001-exec-1", cn.valuetodays.lab.IndexService.saveUser(), line=14 bci=0 ## 查看本地变量,有方法参数和本地变量 http-nio-50001-exec-1[1] locals Method arguments: name = "a1111111b3333333333333" Local variables: ## 下一步 http-nio-50001-exec-1[1] next > Step completed: "thread=http-nio-50001-exec-1", cn.valuetodays.lab.IndexService.saveUser(), line=16 bci=3 ## 再次查看本地变量 http-nio-50001-exec-1[1] locals Method arguments: name = "a1111111b3333333333333" Local variables: sql = "insert into user(name) values(?)" ## 下一步 http-nio-50001-exec-1[1] next ## 再次查看本地变量,此时就对应代码中的catch处,可以看到变量e http-nio-50001-exec-1[1] locals Method arguments: name = "a1111111b3333333333333" Local variables: sql = "insert into user(name) values(?)" e = instance of org.springframework.dao.DataIntegrityViolationException(id=6545) ## 查看变量e的内容,可以知道是字段太长了 http-nio-50001-exec-1[1] print e e = "org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into user(name) values(?)]; Value too long for column """NAME"" VARCHAR(16)": "'a1111111b3333333333333' (22)"; SQL statement: insert into user(name) values(?) [22001-199]; nested exception is org.h2.jdbc.JdbcSQLDataException: Value too long for column """NAME"" VARCHAR(16)": "'a1111111b3333333333333' (22)"; SQL statement: insert into user(name) values(?) [22001-199]" ## 退出 http-nio-50001-exec-1[1] exit [root@server201 ~]# ``` 小伙伴们可以操作一下。 ## 参考 - jdb with javase <https://blog.csdn.net/xixingzhe2/article/details/109384076> - 官方文档:<https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jdb.html> ## 其它 使用jdb命令连接到远程应用后输入`help`可以查看所有命令。 在docker中,我尝试了多种connectors,发现只有com.sun.jdi.SocketAttach能正常使用,理论上说在同一台机器(且同一个linux用户)中,提供进程id就应该能连接了,但是不行,我使用的docker镜像是openjdk:8-jdk-slim。但在linux系统中就能正常。 ```bash # docker中并未成功,linux系统中正常 jdb -connect com.sun.jdi.ProcessAttach:pid=18405,timeout=10 # docker中并未成功,,linux系统中正常 jdb -connect sun.jvm.hotspot.jdi.SAPIDAttachingConnector:pid=18405 ```
我是张三
2022年6月29日 15:20
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
eblog
Markdown文件
分享
链接
类型
密码
更新密码