关于java:千万不要在方法上打断点有大坑

49次阅读

共计 7071 个字符,预计需要花费 18 分钟才能阅读完成。

你好呀,我是歪歪。

我上周遇到了一个莫名其妙的搞心态的问题,节约了我好几个小时。

气死我了,拿这几个小时来敲(摸)代(摸)码(鱼)不香吗?

次要是最初问题的解决形式也让我特地的无语,越想越气,写篇文章吐槽一下。

先说论断,也就是题目:

在本地以 Debug 模式启动我的项目的时候,千万不要在办法上打断点!千万不要!

首先什么是办法断点呢?

比方这样的,打在办法名这一行的断点:

你点击 IDEA 外面的上面这个图标,View Breakpoints,它会给你弹出一个框。

这个弹框外面展现的就是以后我的项目外面所有的断点,其中有一个复选框,Java Method Breakpoints,就是以后我的项目外面所有的“办法断点”:

那么这个玩意到底有什么坑呢?

当我的项目以 Debug 模式启动的时候 ,十分十分十分重大的拖慢启动速度。

给你看两个截图。

上面这个是我本地的一个非常简单的我的项目,没有办法断点的时候,只有 1.753 秒就启动实现了:

然而当我加上一个办法断点的时候,启动工夫间接来到了 35.035 秒:

从 1.7 秒间接飙升到 35 秒,启动工夫涨幅 2000%。

你说遭不遭得住?

遭不住,对不对。

那么我是怎么踩到这个坑的呢?

一个共事说他我的项目外面遇到一个匪夷所思的 BUG,想让我帮忙一起看看。

于是我先把我的项目拉了下来,而后简略的看了一下代码,筹备把我的项目先在本地跑起来调试一下。

然而半个小时过来了,我的项目还没起来。我问他:这个我的项目本地启动工夫怎么这么长呢?

他答:失常来说半分钟应该就启动起来了呀。

接着他还给我演示了一下,在他那边的确 30 多秒就启动胜利了。

很显著,一样的代码,一个中央启动慢,一个中央启动快,首先狐疑环境问题。

于是我筹备依照上面的流程走一次。

查看设置 -> 清空缓存 -> 换 workspace -> 重启 -> 换电脑 -> 辞职

我查看了所有的配置、启动项、网络连接什么的,确保和他本地的环境是截然不同的。

这一套操作下来,差不多一小时过来了,并没有找到什么脉络。

然而那个时候我一点都不慌,我还有终极绝招:重启。

毕竟我的电脑曾经好几个月没有敞开过了,重启一下也挺好的。

果然,重启了电脑之后,还是没有任何扭转。

正在焦头烂额之际,共事过去问我啥进度了。

我能怎么说?

我只能说:从工夫上来说应该解决了,然而实际上我连我的项目都还没启动胜利。

听到这话,他坐在我的工位,筹备帮我看一下。

半分钟之后,一个神奇的场景呈现了,他在我的电脑上间接就把我的项目启动起来了。

一盘问,他并没有以 Debug 的模式启动,而是间接运行的。

用脚趾头想也晓得,必定是 Debug 模式在搞事件。

而后基于面向浏览器编程的准则,我当初有了几个关键词:IDEA debug 启动迟缓。

而后发现有很多人遇到了相似的问题,解决办法就是启动的时候勾销我的项目外面的“办法断点”。

然而,遗憾的是,没有大多数文章都是说这样做就好了。然而并没有通知我为什么这样做就好了。

我很想晓得为什么会有这个坑,因为我用办法断点用的还是很多的,要害是以前在应用的过程中齐全没有留神到还有这个坑。

“办法断点”还是十分实用的,比方我轻易个例子。

之前写事务相干的文章的时候,提到过这样的一个办法:

java.sql.Connection#setAutoCommit

setAutoCommit 这个办法有好几个实现类,我也不晓得具体会走哪一个:

所以,调试的时候能够在上面这个接口打上一个断点:

而后重启程序,IDEA 会主动帮你判断走那个实现类的:

然而须要特地阐明的是,不是所有的办法断点都会导致启动迟缓的问题。至多在我本地看起来是这样的。

当我把办法断点加在 Mapper 的接口外面的时候,能稳固复现这个问题:

当把办法断点加在我的项目的其余办法上的时候,不是必现的,偶然才会呈现这个问题。

另外,其实当你以 Debug 模式启动且带有办法断点的时候,IDEA 是会弹出这个揭示,通知你办法断点会导致 Debug 迟缓的问题:

然而,真男人,从不看揭示。反正我是间接就疏忽了,基本没有关怀弹窗的内容。

至于为什么会在 Mapper 的接口上打办法断点?

都怪我手贱,行了吧。

到底为什么

在找答案的过程中,我发现了这个 idea 的官网社区的链接:

https://intellij-support.jetb…

这个贴子,是 JetBrains Team 公布的,对于 Debug 性能可能会导致的性能迟缓的问题。

在这个帖子中,第一个性能点,就是 Method breakpoints。

官网是怎么解释这个问题的呢?

我给你翻译一波。

Method breakpoints will slow down debugger a lot because of the JVM design, they are expensive to evaluate.

他们说因为 JVM 的设计,办法断点会大大降低调试器的速度,因为这玩意的“evaluate”老本很高。

evaluate,四级单词,好好记一下,考试会考:

大略就是说你要用办法断点的性能,在启动过程中,就波及到一个对于该断点进行“评估”的老本。老本就是启动迟缓。

怎么解决这个“评估”带来的老本呢?

官网给出的计划很简略粗犷:

不要应用办法断点,不就没有老本了?

所以,Remove,完事:

Remove method breakpoints and consider using the regular line breakpoints.

删除办法断点并思考应用惯例的 line breakpoints。

官网还是很贴心的,怕你不晓得怎么 Remove 还专门补充了一句:

To verify that you don’t have any method breakpoints open .idea/workspace.xml file in the project root directory (or <project>.iws file if you are using the old project format) and look for any breakpoints inside the method_breakpoints node.

能够通过上面这个办法去验证你是否关上了办法断点。

就是去 .idea/workspace.xml 文件中,找到 method_breakpoints 这个 Node,如果有就 Remove 一下。

而后我看了一下我我的项目外面对应的文件,没有找到 method_breakpoints 关键字,然而找到了上面这个。

应该是文档产生了变动,问题不大,反正是一个意思,

其实官网给出的这个办法,尽管逼格略微高一点,但还是我后面给的这个操作更简略:

针对“到底为什么”这个问题。

在这里,官网给的答复,特地的含糊:because of the JVM design。

别问,问就是因为 JVM 设计如此。

我感觉这不是我想要的答案,然而好在我在这个帖子上面找到了一个“坏事之人”写的回复:

这个坏事之人叫做 Gabi 老铁,我看到他回复的第一句话“I made some research”,我就晓得,这波稳了,找对中央了,答案必定就藏在他附上的这个链接外面。

Gabi 老铁说:哥子们,我钻研了一下这个办法断点为啥会慢的起因,钻研报告在这里:

http://www.smartik.net/2017/1…

他甚至还来了一个概要:To make the long story short,长话短时。

他真的很贴心,我哭死。

他首先指出了问题的根本原因:

it seems that the root issue is that Method Breakpoints are implemented by using JDPA’s Method Entry & Method Exit feature.

基本问题在于办法断点是通过应用 JDPA 的 Method Entry & Method Exit 个性实现的。

有同学就要问了,JDPA,是啥?

是个宝贝:

https://docs.oracle.com/javas…

JPDA,全称 Java Platform Debugger Architecture。

IDEA 外面的各种 Debug 性能,就是基于这个玩意来实现的。

不懂也没关系,这个货色面试又不考,在这里晓得有这个技术就行。

接着,他用了四个 any 来实现了跳句四押:

This implementation requires the JVM to fire an event each time any thread enters any method and when any thread exits any method.

这个实现,要求 JVM,每次,在任何(any)线程进入任何(any)办法时,以及在任何(any)线程退出任何(any)办法时触发事件。

好家伙,这不就是个 AOP 吗?

这么一说,我就明确为什么办法断点的性能这么差了。要触发这么多进入办法和退出办法的事件,可不得消耗这么多工夫吗?

具体的细节,他在后面说的钻研报告外面都写分明了,如果你对细节感兴趣的话,能够征询浏览一下他的那篇报告。

话说他这个报告的名字也起的挺唬人的:Method Breakpoints are Evil。

我带你看两个要害的中央。

第一个是对于 Method Entry & Method Exit 的:

  • IDE 将断点增加到其外部办法断点 list 中
  • IDE 通知前端启用 Method Entry & Method Exit 事件
  • 前端(调试器)通过代理将申请传递给 VM
  • 在每个 Method Entry & Method Exit 事件中,通过整个链将告诉转发到 IDE
  • IDE 查看其办法断点 list 是否蕴含以后的这个办法。
  • 如果发现蕴含,阐明这个办法上有一个办法断点,则 IDE 将向 VM 发送一个 SetBreakpoint 申请,打上断点。否则,VM 的线程将被开释,不会产生任何事件

这里是表明,后面我说的那个相似 AOP 的略微具体一点的操作。

外围意思就一句话:触发的事件太多,导致性能降落厉害。

第二个要害的中央是这样的:

文章的最初给出了五个论断:

  • 办法断点 IDE 的个性,不是 JPDA 的个性
  • 办法断点是真的邪恶,evil 的一比
  • 办法断点将极大的影响调试程序
  • 只有在真正须要时才应用它们
  • 如果必须应用办法作为断点,请思考敞开办法退出事件

后面四个点没啥说的了。

最初一个点:思考敞开办法退出事件。

这个点验证起来非常简单,在办法断点上右键能够看到这个选项,Method Entry & Method Exit 默认都是勾选上了:

所以我在本地轻易用一个我的项目验证了一下。

关上 Method Exit 事件,启动耗时:113.244 秒。

敞开 Method Exit 事件,启动耗时:46.754 秒。

你别说,还真有用。

当初我大略是晓得为什么办法断点这么慢了。

这真不是 BUG,而是 feature。

而对于办法断点的这个问题,我顺便在社区搜寻了一下,最早我追溯到了 2008 年:

这个老哥说他调试 Web 程序的速度慢到无奈应用的水平。他的我的项目只启用了一行断点,没有办法断点。

申请大佬帮他看看。

而后大佬帮他一顿剖析也没找到起因。

他本人也特地的纳闷,说:

我啥也没动,太奇怪了。这玩意有时能够,有时不行。

像不像一句经典台词:

然而问题最初还是解决了。怎么解决的呢?

他本人说:

的确是有个办法断点,他也不晓得怎么打上这个断点的,可能和我一样,是手抖了吧。

意外播种

在后面呈现的官网帖子的最上面,有这样的两个链接:

它指向了这个中央:

https://www.jetbrains.com/hel…

我把这部分链接都关上看了一遍,通过鉴定,这可真是好货色啊。

这是官网在手摸手教学,教你如何应用 Debug 模式。

我之前看过的一些调试小技巧相干的文章,原来就是翻译自官网这里啊。

我在这里举两个例子,算是一个导读,强烈推荐那些在 Debug 程序的时候,只晓得不停的下一步、跳过以后断点等这样的基本操作的同学去仔细阅读,入手实操一把。

首先是这个:

针对 Java 的 Streams 流的调试。

官网给了一个调试的代码示例,我做了一点点微调,你粘过来就能跑:

class PrimeFinder {public static void main(String[] args) {IntStream.iterate(1, n -> n + 1)
                .limit(100)
                .filter(PrimeTest::isPrime)
                .filter(value -> value > 50)
                .forEach(System.out::println);
    }
}

class PrimeTest {static boolean isPrime(int candidate) {
        return candidate == 91 ||
                IntStream.rangeClosed(2, (int) Math.sqrt(candidate))
                        .noneMatch(n -> (candidate % n == 0));
    }
}

代码逻辑很简略,就是找 100 以内的,大于 50 的素数。

很显著,在 isPrime 办法外面对 91 这个非素数做了非凡解决,导致程序最终会输入 91,也就是出 BUG 了。

尽管这个 BUG 高深莫测,然而不要笑,要忍住,要伪装不晓得为什么。

当初咱们要通过调试的形式找到 BUG。

断点打在这个地位:

以 Debug 的模式运行的时候,有这样的一个图标:

点击之后会有这样的一个弹窗进去:

下面框起来的是对应着程序的每一个办法调用程序,以及调用实现之后的输入是什么。

上面框起来的这个“Flat Mode”点击之后是这样的:

最左边,也就是通过 filter 之后输入的后果。

外面就蕴含了 91 这个数:

点击这个“91”,发现在通过第一个 filter 之后,91 这个数据还在。

阐明这个中央出问题了。

而这个中央就是后面提到的对“91”做了非凡解决的 isPrime 办法。

这样就能有针对性的去剖析这个办法,放大问题排除范畴。

这个性能怎么说呢,反正我的评论是:

总之,以上就是 IDEA 对于 Streams 流进行调试的一个简略示例。

接着再演示一个并发相干的:

官网给了这样的一个示例:

public class ConcurrencyTest {static final List a = Collections.synchronizedList(new ArrayList());

    public static void main(String[] args) {Thread t = new Thread(() -> addIfAbsent(17));
        t.start();
        addIfAbsent(17);
        t.join();
        System.out.println(a);
    }

    private static void addIfAbsent(int x) {if (!a.contains(x)) {a.add(x);
        }
    }
}

代码外面搞一个线程平安的 list 汇合,而后主线程和一个异步线程别离往这个 list 外面塞同一个数据。

依照 addIfAbsent 办法的意思,如果要增加的元素在 list 外面存在了,则不增加。

你说这个程序是线程平安的吗?

必定不是。

你想想,先判断,再增加,经典的非原子性操作。

然而这个程序你拿去间接跑,又不太容易跑出线程不平安的场景:

怎么办?

Debug 就来帮你干这个事儿了。

在这里打一个断点,而后右键断点,抉择“Thread”:

这样程序跑起来的时候主线程和异步线程都会在这个中央停下来:

能够通过“Frames”中的下拉框别离抉择 Debug 主线程还是异步线程。

因为两个线程都执行到了 add 办法,所以最终的输入是这样的:

这不就呈现线程不平安了吗?

即便你晓得这个中央是线程不平安的,然而如果没有 Debug 来帮忙调试,要通过程序输入来验证还是比拟艰难的。

毕竟多线程问题,大多数状况下都不是每次都能必现的问题。

定位到问题之后,官网也给出了正确的代码片段:

好了,说好了是导读,这都是基本操作。还是那句话,如果感兴趣,本人去翻一下,跟着案例操作一下。

就算你看到有人把 Debug 源码,玩出花来了,也无外乎不过是这样的几个根底操作的组合而已。

回首往事

让咱们再次回到官网的“对于 Debug 性能可能会导致的性能迟缓的问题”这个帖子外面:

当我看到方框外面框起来的“Collections classes”和“toString()”办法的时候,眼泪都快下来了。

我最早开始写文章的时候,已经被这个玩意坑惨了。

三年前,2019 年,我写了这篇文章《这道 Java 根底题真的有坑!我也没想到还有续集。》

过后 Debug 调试 ArrayList 的时候遇到一个问题,我一度认为我被质子烦扰了:

一句话汇总就是在单线程的状况下,程序间接运行的后果和 Debug 输入的后果是不一样的。

过后我是百思不得其解。

直到 8 个月后,写《JDK 的 BUG 导致的内存溢出!反正我是没想到还能有续集》这篇文章的时候才偶然间找到问题的答案。

根本原因就是在 Debug 模式下,IDEA 会主动触发汇合类的 toString 办法。而在某些汇合类的 toString 办法外面,会有诸如批改头节点的逻辑,导致程序运行后果和预期的不匹配。

也就是对应这句话:

翻译过去就是:老铁请留神,如果 toString 办法中的代码更改了程序的状态,则在 debug 状态下运行时,这些办法也能够更改应用程序的运行后果。

最初的解决方案就是敞开 IDEA 的这两个配置:

同时,我也在官网文档中找到了这个两个配置的解释:

https://www.jetbrains.com/hel…

次要是为了在 Debug 的过程中用更加敌对的模式显示汇合类。

啥意思?

给你看个例子。

这是没有勾选后面说的配置的时候,map 汇合在 Debug 模式下的样子:

这是勾选之后,map 汇合在 Debug 模式下的样子:

很显著,勾选了之后的样子,更加敌对。

最初,我的文章全网首发都在我的公众号外面哦。欢送关注:

正文完
 0