快速计算表达式
都知道调试面板里的Evaluate Expression可以计算表达式/变量的值,但那毕竟还得弹个窗稍显麻烦,其实还有更为方便的方式:
用鼠标操作,效率指数级提升。这个操作方式是:鼠标指针选中表达式(IDEA智能自动选中) + 鼠标左键单击。当然喽,如果你想执行自定义的不存在于代码中的表达式,那必须调起窗口来操作。
Stream流调试
Java 8的流行,彻底让流式编程走进我们的视野。使用Stream编程的好处众多,但一直被大家诟病的是难以阅读和难以调试,特别是后者。
为了调试它,我们经常需要插入其它断点,并分析流中的每个转换,不可为不麻烦。还好IDEA提供了处理该痛点的“能力”:当调试器在Stream API调用链之前或之内停止时,点击Trace Current Stream Chain这个图标即可以“非常好看”的图形化方式展示出来,一目了然:
主动抛出异常
需求场景:你写了一个全局异常组件,现在想测试它生效情况如何,那么时候你就需要主动抛出这种异常,一般的做法是形如这样:
// 自己在程序内主动throw一个 throw new NullPointerException(); // 或者构建个表达式 int i = 1/0;
这种做法均有一定的代码侵入性,用后还得删除。其实IDEA还提供了一种更为优雅的解决方案:
掌握了IDEA断点调试的基本技能,下面进入到本文深水区:断点类型。难度不高,依旧是使用层面的事,但由于很多同学并不知道,因此是你用于超车的好材料。
四大断点类型
对于打断点,估计大部分同学都只会左边鼠标单击这种最基础的方式。所以,看到这个小标题估计你得再懵一次吧。what?断点还有种类?
若你也是只在代码左边鼠标单击打上“小红点”,然后嘎嘎就是干,空中转体720度向后翻腾三周半…一把唆的选手,那么接下来就坐稳喽,准备发车。
这么个姿势也许能帮你定位50%以上的问题,但还有另外一半的case呢?如for循环调试,Stream流调试,lambda调试、异常调试等这些场景,用那“一把唆”的方式就很难搞定甚至说搞不定了。断点是帮我们快速定位问题的,不同的场景打上合适的断点将能事半功倍。
殊不知,IDEA给我们开发者提供了非常的断点类型,以应对不同场景下的调试。在对应的场景下使用合适正确的断点类型,能够大大提高调试的效率,从而别人加班你下班,效率就是时间,而时间就是生命。
如图,IDEA把断点分为四大类型(截图中只有三类):
- Line breakpoint(行断点):图中红色小圆圈。顾名思义,在指定代码行设置断点
- Field watchpoint(属性断点):图中红色小眼睛。打在类的属性(static or 非static)上的断点,作用是在该属性读取和写入时激活
- Method breakpoint(方法断点):图中红色小菱形。标记在方法签名的那一行,在该方法执行的入口/出口处被激活
- Exception breakpoint(异常断点):红色小闪电。这是一个特殊但很好用的断点,当程序抛出指定异常时会激活异常断点。和其它断点不同,异常断点是项目全局的,它不需要打在具体某一行上
下面就到了“啃硬骨”的时候了,来吧。
行断点Line breakpoint
使用得最最最广泛的断点类型,平时大部分情况下都使用此种断点。
从“教程”中可以看到该断点有很多的设置项,也就是有很多的断点参数可以配置,来了解下。
断点参数
因为这是第一个介绍断点参数的类型,因此会说得详细些,这样子后面相同功能的参数就不用再赘述了。对照这个截图页:
- Enabled:不解释。但需注意:若此项不勾选上,小红点并不会消失,而是由实心的变为空心的,当然喽,一般情况下并不会动此项
- Suspend:众所周知,断点激活时会阻塞程序的继续运行,从而阻塞当前线程。但是当你发现它是个复选框的时候,有没有被诧异到?并且,并且,并且你还可以根本就不勾选它,有何区别:
- 若不勾选选中:此断点相关活动(如打日志等)依旧正常进行,只是不阻塞进程了
- 若勾选中:
- All(默认):阻塞该程序内所有线程
- Thread:只阻塞当前断点所在线程
如上图,不勾选Suspend:线程14和线程15正常运行,“畅通无阻”
如上图,勾选Suspend-All:在断点处,所有线程都被阻塞了,统一给我等待。
如上图,勾选Suspend-Thread:method1的线程被阻塞,但是并不影响另外一个线程调用method2。
试想一下,既然“勾选Suspend-Thread”影响更小,那为何IDEA默认帮你选择All而不是Thread呢?原因是这样子的:调试的目的就是让程序“慢下来”,最好是静止下来方便分析问题。否则,其它线程如果仍旧继续保持执行的话,可能一会这个请求改掉这个数据一会改掉那个数据,增加了不可控性。不确定的增加从而大大增加调试难度和定位问题的难度,所以索性上个“同步锁”来得省心,因此默认选中Suspend-All是合理为之。
说明:很多时候我们需要用本机连接测试环境打断点进行远程调试,若在这个case下强烈建议你使用Thread模式,否则你懂的
- Condition: 断点被激活的条件。你可以在此处书写表达式,只有表达式返回true时此断点才会被激活
- 条件断点严格来讲不属于一种断点类型,属于断点参数决定的,很多类型的断点都可加条件
- Log:它有三个选项,是checkbox哦。也就是说可都选,也可都不选,默认一个都不选
- Breakpoint hit message:断点激活时输出提示日志
- Stack trace:断点激活时输出程序调用栈信息
- Evaluate and log:选择需要输出计算表达式的值。你可选择当前可达的变量,如本例的main函数入参args等
- remove once hit:断点激活一次后就立马给移除喽,也就是所谓的临时行断点,下面来介绍下它
还有窗口里最右边的这块条件
见名之意,一系列过滤器:过滤实例、过滤类、过滤调用者等等,一般这些们几乎不会使用(至少我目前是还没用过的),所以就一笔带过。
使用场景
行断点一般配合单步调试一起使用,在看框架源码、定位基础问题等使用得特别多,是最需要掌握的一种断点类型,没得商量。
临时行断点Temporary line breakpoint
它也属于行断点的一种,只是参数不一样而已。由于它比较特殊,所以单摘出来说道说道。创建普通行断点,然后把Remove once hit复选框勾选上即是一个临时行断点,效果如下:
这种断点类型,实际使用场景其实很少。
属性断点Field watchpoint
此类断点是打在属性上的,成员属性和静态属性均可。它不是小红点,而是个红色“小眼睛”。
断点参数
如图,此种断点类型特有个watch参数,两个可选值的含义为:
- Filed Access:读取此属性时(写入时不管)
- Filed madification:写入此属性时(读取时不管)
使用场景
当想知道xxx属性的赋值是谁时,由于程序太庞杂没法知道断点打哪儿从哪开始跟踪,这个时候使用属性类型的断点一下子就搞定了,非常的方便。
方法断点Method breakpoint
断点必须打在方法签名的那一行,颜色形状是个红色的小菱形。
断点参数
Watch有三个可选值:
- Emulated:仿真。作用:提高调试性能,因此默认情况下使用。官方建议:仅在调试远程代码或在没有行号信息的native方法或类中设置断点时,才建议禁用此选项
- Method entry:进入方法时激活断点
- Method exit:出去方法时激活断点
若entry和exit都勾选,那在进入之后和出去之前都会激活断点
使用场景
对于此种断点类型,可能你会说没啥卵用。毕竟自己在方法头尾打个行断点就能达到同样效果,没必要单独搞个类型嘛。
其实,它的杀手锏级使用场景是把此种类型断点打在接口方法上,这样子不管哪个实现类方法被调用,都会激活断点,是不是特别给力。
异常断点Exception breakpoint
比较小众,但并不代表不重要。在我理解它比较小众,可能大多数同学不知道如何打一个异常断点,因为它不是鼠标单击就能轻松搞定。
上面介绍了异常断点它是一种全局断点类型,因此并不能在代码处直接单击,而是只能在管理窗口里统一添加:
和其它断点类型相比,至少有如下不一样:
- 创建断点只能通过断点管理窗口创建,而不能通过鼠标点击方式
- 创建完成后,代码栏处不会有任何显示(没有红色小图标),直到它被激活时才会出现红色小闪电
- 异常断点作用于全局:本例中任何地方抛出了NullPointException都会激活此断点
断点参数
Notification有两个可选值:
Catch excetion:只有当你自己try-catch了这个异常才会激活断点
Uncatch excetion:只有当你自己不try-catch时才会激活断点
默认情况下这两个都会被勾选上,也就是说任何情况下发生此异常,都会激活断点。
使用场景
知晓了异常断点的作用和触发条件,使用场景就有啦。比如当你的程序抛出了一个异常,但是一时半会你并不知道是哪行代码引起的,这个时候通过增加异常断点的方式可以实现迅速的问题定位。
4种断点图标对比
每种断点类型都有自己对应的图标,且有不同的状态。我从官网趴了一张对比图,总结得特别好,在这里一并分享给你:
远程调试(远程Debug)
现在大都是微服务架构方式,每个微服务一般会有N多个上/下游依赖,如此以至于给调试带来了很大困难,毕竟你几乎不可能在本地同时把依赖都启起来用IDEA做调试。所以,远程调试来了,它是调试分布式系统的一个利器。
远程调试:顾名思义,使用本地IDEA调试远程代码(一般为QA环境,线上环境不可能开启调试端口的)。那么如何开启远程调试呢?
开启步骤
开启远程调试只需要两步即可:
第一步:让远程部署的那个应用支持远程调试,也就是暴露远程调试端口。方式方法为在应用启动时加上对应的JVM参数即可,JDK版本不同参数也不一样
- JDK 9+:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*😒{debug_port}
- JDK 5-8:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=${debug_port}
- JDK 4:-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${debug_port}
- JDK 3-:-Xnoagent -Djava.compiler=NONE -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${debug_port}
第二步:用IDEA创建一个remote运行配置,填上远程主机的ip + 暴露的调试端口即可。操作路径为:Edit Configurations -> Add New Configuration ->
万事俱备,点击debug运行,控制台里能看到如下字样就证明你链接成功了:
值得注意的是:远程调试时请确保你本地代码和远程代码一模一样,以达到最佳效果。
传统Tomcat如何开启远程调试?
若你是个Spring Boot应用,那么在jar -jar时加上JVM参数即可,那么如果是要使用传统的tomcat方式部署呢?这个时候找到传统tomcat的启动脚本startup.sh:
#!/bin/sh os400=false ... PRGDIR=`dirname "$PRG"` EXECUTABLE=catalina.sh ... exec "$PRGDIR"/"$EXECUTABLE" start "$@"
为了加上咱们的JVM参数,只需要在exec xxx
之前添加一个变量值即可(以JDK8为例):
JPDA_OPTS='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=具体的端口号
注意:这个key名称必须是JPDA_OPTS。
有好奇心的你可能不禁就要问了:为何加个JPDA_OPTS参数就行了呢?也没见exec xxx使用它呀,其实不然,下面简单解释下,不展开。
exec执行时引用了变量 $EXECUTABLE,它代表的是就是catalina.sh这个文件,该文件里面有大量变量判断脚本,当然包括负责对JPDA_OPTS解释:
#!/bin/sh cygwin=false darwin=false ... if [ "$1" = "jpda" ] ; then if [ -z "$JPDA_TRANSPORT" ]; then JPDA_TRANSPORT="dt_socket" fi if [ -z "$JPDA_ADDRESS" ]; then JPDA_ADDRESS="localhost:8000" fi if [ -z "$JPDA_SUSPEND" ]; then JPDA_SUSPEND="n" fi if [ -z "$JPDA_OPTS" ]; then JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND" fi CATALINA_OPTS="$JPDA_OPTS $CATALINA_OPTS" shift fi ...
关于JVM调试平台JPDA更多知识点,可自行用谷歌百度一下学习学习
嵌入式Tomcat如何开启远程调试?
这不就是Spring Boot应用形式麽?所以,如何开启,不用再废话了吧~
总结
人和动物的最大区别之一是人会使用工具,且善于使用工具。工具被创造出来,使命就是提效的,毕竟我们不可能用记事本去写Java程序吧。
IntelliJ IDEA作为最为流行的JVM平台IDE,我们应该尽可能的去挖掘出它的效用,既然作为集成开发环境,其实很多功能都可以一站式搞定,在一个平台里做很多数据都能打通。比如IDEA的rest接口调试、数据库映射、Shell终端等等,应付平时的开发一般搓搓有余,推荐使用,毕竟软件启得越多电脑越卡不是。
用IDEA和会用IDEA是两个层次,除了代码本身,最常用的开发工具也是值得花番心思的。大道至简,知易行难,知行合一,得到功成!
本文思考题
本文所属专栏:IDEA,后台回复专栏名即可获取全部内容,已被https://www.yourbatman.cn收录。
看完了不一定懂,看懂了不一定会。来,文末3个思考题帮你复盘:
- 断点能打在类上吗?
- IDEA能设置哪几种类型的断点呢?各有什么场景?
- 如何用IDEA debug调试测试环境的应用?