IDEA调试:if条件为false却停在continue?断点排查指南

📅 发布时间:2026/10/9 6:06:36
IDEA调试:if条件为false却停在continue?断点排查指南
用 IDEA 调 Java 的时候你一定遇到过这种场面程序跑到了一个continue语句上断点停在那里你觉得不可思议——明明前面的 if 条件是 false按逻辑根本不该走进来怎么断点就“走到”了continue我最早遇到这类问题时也怀疑过 IDEA 的断点机制甚至怀疑是不是编译器把代码编译坏了。后来排查多了才明白绝大多数情况下不是 IDEA 的锅而是调试视角、断点设置和代码路径这三件事在跟我们捉迷藏。这篇文章就是把这类问题系统总结一下。不管你是刚学 Debug 的新手还是每天跟复杂业务逻辑搏斗的老手只要你是用 IDEA社区版、旗舰版都一样做 Java 或者 Android 开发这篇对你会很有帮助。我会先复现现象再拆原因最后给一套可复现的排查步骤让你下次再遇到“if 条件看起来是 false断点却停到 continue”的时候几分钟内就能定位不用再一个个变量去猜。1. 先复现两类典型现象别被“断点走到 continue”带偏在排查一个调试问题之前最好是先把“现象”精确化。“断点‘走到’continue”其实是一句很模糊的口语背后可能对应着两类完全不同、但都表现得差不多的现场。先把这两种现场区分开后面的排查才能有的放矢。1.1 第一种现场continue 那行本身有个无条件断点第一种现象最简单也最常见continue 那一行上站着一个独立的断点而且这个断点没有设置任何条件。比如下面这段代码public static void main(String[] args) { ListString words Arrays.asList(hello, world); for (String word : words) { if (word.length() 3) { if (skip.equals(word)) { continue; // 断点打在这里红色圆点无任何条件 } System.out.println(处理 word); } } }看上去逻辑很明确continue 只在word等于skip时才会执行而words里根本没有skip理论上这一行不应该被命中。结果你 Debug 一跑断点偏偏就停在了 continue 行而你检查word的值又是hello于是你心里冒出一个念头外层 if 条件是falsecontinue 怎么还会走这里要先纠正一个潜意识里的错误关联你看到“断点停在了 continue 行”不一定是“程序真的执行了这条 continue”。在 JVM 体系里断点注册的粒度是字节码指令地址IDE 显示的“哪一行”只是根据 class 文件里的 LineNumberTable 映射出来的结果。编译器在生成循环和 if-else 代码时经常把几个源码行的字节码对应到同一个行号条目上尤其遇到 for-each、嵌套 if、continue 这种短跳转密集的代码行号映射会非常“粗”。IDEA 拿到 JDI 事件后尝试把命中的字节码地址翻译成源码行号翻译结果不一定是你能在源码上精确定位的那一行。在实际工作中我遇到最多的“第一种现场”其实是这样的代码里并不止一个 continue而是循环体里有多个 if、多个 continue你在continue字样上打了一个断点但 ID 高亮时你可能没有注意到“这个 continue 其实不在你以为的那个 if 块里”或者编辑器里有两处 continue图标重叠了。调试器暂停时你只看到了“有个 continue”就联想起你关心的那个 if 分支。这种“认错行”比断点奇怪的“行号错位”常见得多。后面我会用案例二详细展开。1.2 第二种现场Resume 之后被下一个断点拦住第二种更贴近标题描述的“走到 continue”现场你在 if 那一行打了一个断点然后在循环体后面你还设置了断点比如在 continue 行上或者循环体内的某行也有断点。当 if 条件判断为 false 时你停在了 if 行然后在调试工具栏点“Resume Program”绿色三角希望程序直接跑到后面需要的位置。结果程序并没有像你预想的那样跳出 if 块而是立刻停在了 continue 行——你本能地以为“if 为 false 却进入了 if 块”。但其实这只是普通的多断点行为。IDEA 的 Resume 是“继续执行直到遇到任意一个断点”它不是“执行完当前分支”。continue 行上的断点会被命中即使 continue 本来不属于你刚才检查的那个 if 块。如果这个 continue 是由另一个 if 分支触发的那么你会发现你正在调试的对象状态已经不是刚才那个了或者你切换到了另一个线程的栈。因此遇到“看起来不该走到 continue”的问题第一件事请按CtrlShiftF8Windows/Linux或CmdShiftF8macOS打开断点列表看看当前项目里到底有哪些断点哪些是带条件的哪些是裸的。这不是小题大做因为一半以上的所谓灵异现象最后都是某行残留了一个你忘了的断点。操作行为容易产生的误会Step Over执行当前行并在下一行暂停只跳一行不会跳到很远位置Resume继续执行直到下一个断点或结束跳过多个断点让你误以为是当前逻辑路径2. 五个最容易误判 if 条件值的地方下面进入干货核心为什么你“觉得” if 条件为 false。即使断点停在了 continue也未必说明逻辑出错了。我们要梳理五个误区。这些误区单独看都不起眼但组合在一起就是“灵异事件”的完整配方。2.1 变量面板的值不一定等于 if 判断时用到的值断点暂停在 continue 行时if 判断已经执行完了时间点已经过去了。这时候你在 Variables 面板看到某个变量的当前值跟 if 判断那一刻的值不一定相同。比如 if 条件是一个方法调用的返回值if (validator.check(item)) { continue; }validator.check(item)可能内部修改了 item 的属性。举个稍微极端但真实存在的例子check 方法进去之后把item.ok置为false然后返回true。如果这时断点停在 continue 行你在变量面板里看到item.ok已经是 false就会产生“if 条件明明 false为什么走进了分支”的错觉。真相是check 返回 true 的那一瞬间条件确实是 true只是这个 true 的状态没被记录下来你看到的已经是“副作用发生之后的 false”了。更隐蔽的是 lambda 和 Stream 操作。很多人的断点停在了一个 Stream 链路的中间环节看到的是当前处理器里的局部值而不是上游传入时的原始值。拿一个已经发生改变后的值去反推之前的判断自然会出现“明明现在是 false怎么刚才走进来了”的错觉。所以当你怀疑断点行为异常时不要只盯着变量面板里的当前值要关注“这个值在什么时机被求值”。2.2 作用域和引用重排你以为的同一个变量可能已经换了Java 源码和调试器视图之间变量名相同不一定代表同一个“变量实例”。循环变量、lambda 捕获变量、for-each 迭代器临时变量在字节码中可能被重排或新建局部变量表槽位。当你在同一处源码行打断点不同迭代轮次可能对应不同的槽位IDEA 勉强把它们的值都显示为同一个名字。如果你习惯于只看“名字”不看“实例 id”就很容易拿另一轮的值去解释这一轮的条件判断。还有常见的一种循环里调用方法方法内部也有一个同名的局部变量。断点从循环帧切换到方法帧后Variables 面板默认显示方法帧的变量名称一样但含义完全不同。我曾经接手过一个老项目代码里到处都是idx、list这种含糊命名排查“为什么走到 continue”就特别容易踩到这个坑断点停了你看到idx是 3但循环变量可能已经走到 8 了因为栈帧显示的根本不是你以为的那一层。2.3 字节码行号映射、热替换与旧 class这一节讲行号映射问题。JVM 规范要求 class 文件里有一个 LineNumberTable它的作用就是把字节码指令映射回源码行号IDE 和调试器主要靠它来实现“在哪一行暂停”。但是这个映射并不是严格一一对应的。编译器优化、代码生成工具Lombok、Spring AOP、MyBatis 动态代理、构建缓存都会导致源码行号与实际执行位置错开。举个常见的例子你在 continue 行打了一个断点IDEA 把断点注册给 JVM 时附带的是“源码某一行”对应的行号。如果这一行对应的字节码指令恰好落在 if 分支判断之前的路径上那么理论上每次执行到这个地址都会停。具体表现就是不打条件断点的 continue 行断点却频繁得不像话停止的位置时而是 continue 行、时而是 if 行非常迷惑。另外IDEA 的热部署HotSwap机制只对方法体的字节码替换比较宽容当你改了方法签名、加了字段、换了内部类IDEA 有时会提示“The classes were reloaded but not all changes will be applied”此时实际运行的可能还是旧版本的字节码。你看到的源码里 continue 逻辑是 A但运行的旧字节码里可能是 B。这个坑在调试中非常隐蔽尤其是多人协作、近实时编译、Gradle 增量构建的情况。遇到这种情况我建议 Build - Rebuild Project然后重新启动 Debug 会话先排除旧 class 的干扰。如果重编译后问题消失那就是热替换和增量编译埋的雷别怀疑自己的代码逻辑。2.4 多线程与线程池干扰你守着的线程可能已经不是主角多线程调试时Debug 窗口默认只会显示当前选中的线程。断点一旦命中IDEA 会暂停命中线程如果有多个线程同时执行同一段代码例如线程池中的 worker、定时任务、web 请求线程你可能在 Frames 面板里切来切去但最怕的是你“以为只有一个线程在执行这段逻辑”事实上其他线程也命中了同一个断点。举个例子你的程序里有一个循环循环里有一句if (condition) { continue; }。这个循环同时被两个线程执行主线程执行到 continue 时条件为 true正常业务需要跳过另一个线程执行到 continue 时条件为 false不该跳过但因为你在 continue 行打了无条件断点两个线程都会暂停。这时候你随手选中的变量视图如果是某个线程的看到条件为 false你就会以为“条件为 false 却走到了 continue”。其实只是你看错了线程。条件断点在这种情况下也不省心IDEA 的条件表达式默认在命中的线程上执行它不会帮你限定线程。如果你需要按线程过滤更可靠的做法是在断点的 Condition 里加上线程名判断或者干脆在代码里用日志输出Thread.currentThread().getName()。2.5 你以为设置了条件断点其实没有这个误区真的非常普遍。很多人“设置条件断点”的操作是在代码行左侧的红色圆点上右键在弹出的菜单里选了一个类似“Enable Breakpoint”的选项或者加了一个断点但 Condition 是空的。只有当你看到断点图标变成“带问号的红色圆点”或者在 Breakpoints 面板里这一行显示了“condition...”的时候它才是一个真正的条件断点。红色圆点本身没有任何条件标识。还有一种情况是你在条件里写了表达式但表达式和你的预期相反。比如你想“只在 isEmpty 的时候停”写成了!item.isEmpty()结果所有非空元素都会命中而空字符串反而跳过。等你看到 continue 行暂停时会把“为什么明明 item 不为空还要 continue”的问题抛给调试器其实是自己把条件写反了。条件断点求值失败也需要特别警惕IDEA 在评估 Condition 表达式时如果抛了异常它会把断点视为“不受条件约束”并直接命中同时在控制台给你打一条 evaluation failed 日志。这个日志很容易被忽略因为你可能没看控制台。所以一旦遇到“条件断点无条件命中”先去看 Debugger 的控制台输出有没有红色报错没有也要瞄一眼事件日志。3. 条件断点怎么设置才真正有效从图标识别到求值失败之前说了那么多目的只有一个让“断点行为”真正反映你的意图。这一章讲工具本身怎么用得对。别小看这一步很多排查工作最后卡住就是因为断点本身没有按预想工作而你还在疯狂怀疑业务代码。3.1 如何设置一个真正“有条件”的断点在 IDEA 中设置条件断点有三步第一步在代码行左侧 gutter 处单击创建一个断点。此时是一个普通断点。 第二步右键点击这个红色圆点或者按下CtrlShiftF8macOS 是CmdShiftF8打开断点属性面板。 第三步在 Condition 输入框中输入一个 Java 布尔表达式比如handle.equals(methodName)、count 5 list.size() 3这种。输入表达式后按下回车断点图标会变化。只有图标上出现一个“?”不同版本略有差异常见是红色圆点带一个问号小图标才说明条件是生效的。另外在断点属性面板中你还能看到 “Condition” 一行具体内容以及 “Suspend” 复选框。更精细的配置是默认勾选 Suspend如果取消勾选这个断点就是日志断点——它不会暂停线程只是在命中时打印信息或堆栈。这里有个操作技巧如果你想临时禁用某条条件断点但不想删除可以取消断点属性面板里的 “Enabled” 复选框如果只是想跳过所有断点可以在代码区任意位置按CtrlShiftF8后在断点列表里批量操作。我不建议大家频繁用“禁用所有断点”这种方式因为禁用状态和条件状态混杂时后面排查更混乱。条件表达式求值时IDEA 使用 Evaluate 表达式在当前栈帧上下文中执行。要注意表达式中不要使用副作用方法例如map.remove(key)、list.add(...)。因为 IDEA 的 Evaluate 是真实的 JVM 求值副作用同样会发生。不要依赖表达式执行顺序的侥幸。比如a ! null a.get() 0这种短路写法是安全的但如果你写a.get() 0 a ! nulla 为 null 时会抛异常。表达式抛异常后断点行为往往会“回退”成无条件命中俗称“条件断点翻车”这是最坑的一个细节。3.2 条件断点求值失败的后果先给结论如果一个条件断点在求值 Condition 时抛出异常IDEA 默认不会重新进入“不命中”状态而是会命中该断点一次并把错误打到 Debugger 控制台。具体表现就是你明明设置了条件断点却鬼使神差地每次都停。这种“每次都停”有时候是灾难级的——比如在每秒执行几千次的方法上加了一个会抛 NPE 的条件断点程序会被暂停到怀疑人生。如果你的条件表达式涉及复杂的对象图建议先在 Evaluate 工具窗口里手工求值一次确认能稳定返回 true/false再放进 Condition。这样做的好处也很明显你可以在 Evaluate 窗口里看到异常堆栈而不是让 IDEA 在内部偷偷吞掉。我经常看到有人在循环里写items.get(index).getName().equals(x)作为断点条件结果 list 里有一个 null 元素条件求值直接 NPE后续所有循环都会命中现场瞬间失控。3.3 命中次数 Pass count和条件配合更省心Pass count 是另一个很好用的断点能力。意思是这个断点在前 N 次命中时都不暂停等第 N1 次命中才暂停。注意Pass count 是在“所有命中次数”上计数的不是“满足条件后的次数”。如果你同时设置了 Condition 和 Pass count行为细节每个 IDEA 版本可能略有差异我看到多数版本是“先判断条件是否成立成立的命中才会计入 Pass count”但为了避免误判我通常只用其中之一要么用条件要么用 Pass count减少不确定性。在循环排查场景中Pass count 适合定位“循环第几次出的问题”假设循环 1000 次你在循环体加了普通断点按个 800 次 Resume 手快也会点到崩溃。设置 Pass count800它会在第 801 次命中时停下来省时省力。不过如果你连第几次命中都不知道那就还是配合条件断点来用比如index 800效果比 Pass count 更直观。3.4 日志断点不暂停也能还原执行路径日志断点是我在排查“看不懂的执行路径”时最推荐的工具。它本质上是断点但勾掉 Suspend 选项后程序不会被暂停只是执行到这一行时打日志。配合“Log evaluated expression”你可以在日志里输出你关心的变量、方法返回值甚至调用栈。举个例子你想知道每次循环走到 continue 时word到底是什么值、线程是谁、栈帧路径是什么。你可以右键 continue 行断点。取消勾选 Suspend。在 “Log evaluated expression” 输入框写continue - word thread Thread.currentThread().getName()。在 “Log stack trace” 处勾选打印调用栈。这么设置以后Debug 运行时不会中断你的程序但控制台会刷出完整的命中信息。比起一次次按 Resume日志断点能一次性给你全貌。这不是偷懒这是调试大型循环与并发问题的标准做法。我见过很多人用System.out.println临时插桩来定位这种问题最后还得删代码、重新编译日志断点完全没有这些烦恼。4. 排查“假性 continue 命中”的四个实操步骤有了前面几章的基础现在可以说说遇到具体问题时我推荐的排查顺序。你不需要每一条都做但按顺序来会节省大量时间。4.1 先清点断点第一步永远是CtrlShiftF8打开断点列表看所有断点。别急着分析代码先回答三个问题现在这个断点是普通断点还是条件断点看图标和属性面板它的条件是什么写错没有它属于哪个文件、哪个模块、哪个方法是不是同名类的错误文件如果你发现 continue 行上有一个完全没有条件且不是你故意加的断点直接删除它再运行一次。很多时候问题当场消失。删除断点的方法是把断点左侧的图标拖动到代码编辑区之外或者右键断点选择 Remove/Delete。4.2 用栈帧和 Drop Frame 回退到 if 判断如果确认断点设置没问题代码也确实在 continue 行暂停了你需要回退到“if 判断发生之前”的那个瞬间。IDEA 调试器有一个 Drop Frame 功能也叫“丢弃帧”。操作方法是在 Frames 面板中选中当前栈顶帧点击工具栏上“Drop Frame”图标一个红色叉/弯曲箭头它会让你“回退”到当前方法的入口处重新开始执行这个方法的当前调用。注意Drop Frame 不能回退到其他线程也不能回退已经过去的系统调用只适用于当前线程当前栈帧。这已经足够解决 90% 的“if 判断时机”问题。回退到方法起点后用 Step Over 一行一行往下走重点观察 if 判断那一行在求值完成时究竟取到了 true 还是 false。这一招比任何口头分析都管用因为它把“判断瞬间”的值真实还原出来了。如果回退后单步观察发现 if 条件其实是 true那么说明你在 continue 行看到的变量值确实不是判断时的值回看 2.1 节的误区就够了。如果你的 IDEA 版本较老没有 Drop Frame 功能也可以采用“加一个临时判断日志”的方式在 if 判断前打一行System.out.println把条件表达式和值打印出来再重新 Debug。这个方法丑是丑但信息绝对准确。4.3 用日志断点记录“路径全貌”当代码路径很复杂、if 分支很多、循环嵌套很深的时候靠一两个断点很难看清整个执行流。这时候我会在关键分支的入口处加日志断点一次性把“谁进来、谁出去、哪个值、哪条线程”全打出来。比如你怀疑 continue 是从某个不该走的分支进来的你可以在外层 if 判断行设置日志断点输出outertrue/false。在内层 continue 行设置日志断点输出innertrue/false 值 堆栈。然后查看控制台的时间顺序就能知道程序是从哪条路进来的。这个方法对比单点断点的最大优势是它不暂停程序不会因为暂停顺序打乱真实并发时序非常适合排查“断点一停就走错线程”的并发问题。日志断点收集到的信息其实比手动单步更全因为它允许你看到“不停下来的正常执行流长什么样”。4.4 多线程干扰的专项排查如果代码里存在线程池、定时任务、异步回调你还需要在断点命中后多做一个动作看当前线程名。在 Debugger 窗口的下方有一个 Threads 面板或叫“Threads Frames”展开它你会看到当前命中的线程名和它的调用栈。如果线程名跟你预期的执行入口不是同一个那先别急着下结论切换到目标线程看看。切换线程不会丢失其他线程的栈帧你可以来回比较多个线程在同一断点处的变量值。如果发现另一个线程满足条件而当前线程不满足说明你此前看到的“false”来自当前线程而真正走进 continue 的可能是另一个线程。这时候最简单的处理方式是在条件断点的 Condition 里加上Thread.currentThread().getName().equals(你关注的线程名)。虽然会有一点性能损耗但能直接锁死目标线程不会再看花眼。如果线程名不固定更稳妥的做法是在代码里临时判断线程名并做标记再配日志断点。总之多线程排查的核心原则是“先分清是谁命中的断点再谈条件的真假”。4.5 代理类、Lombok、行号映射的专项击破如果你排查之后发现断点命中位置和源码逻辑总是“差一行”大概率是行号映射问题。常见触发点是Lombok 生成代码getter/setter/equals/hashCode日志注解等会让方法体与源码行号错位。Spring AOP / MyBatis / 各种字节码增强框架会为代理类生成额外字节码。某些构建工具开启的即时编译JIT策略导致调试信息不全。这类问题的排查思路分两步第一步检查 class 文件的构建信息确认target/classes或build/classes目录下的 class 文件和当前源码是同一版本最彻底的方法就是 Rebuild Project 之后重新 Debug。第二步打开该类的编译产物用javap -l -p ClassName查看 LineNumberTable 与源码行号的对应关系。如果相关方法没有 LineNumberTable或者行号映射明显稀疏那么任何源码断点都可能飘移此时把断点打在更稳的“方法第一行”或者调用入口处往往能缓解。5. 真实踩坑案例与问题速查表这一章我会写几个真我见过很多遍的案例以及一个可以直接保存的速查表。这些案例的代码细节各不相同但背后的原理基本都是前两章的内容。5.1 案例一条件表达式写反continue 命中的全是非预期元素一次同事说他的断点条件明明写了“只拦截空字符串”但每次命中时元素都是非空的。过去看代码他在 continue 行的条件断点写了!s.isEmpty()也就是说他的意思是“我只想在 s 为空时才看”但写反了导致非空的 s 全被拦住。当他看到变量 s 非空时就会觉得“if 条件为 false 却走进了 continue 分支”其实根本原因是断点条件与业务判断方向反了。排查建议在 Evaluate 窗口里把断点条件原样复制出来求值一次看返回的是 true 还是 false如果和你想要的触发条件相反直接反转。这个操作极其便宜一行表达式就能验证但很多人就是懒得做宁可一句“断点坏了”把锅甩给工具。5.2 案例二continue 属于外层 if缩进骗过了眼睛有位朋友贴过一段代码外层 if、内层 if 写了 10 层嵌套continue 写在内层 if 内部的尾巴上但因为大括号风格和大括号缺失视觉上看起来 continue 应该由外层 if 控制。当他给“内层 if 判断行”打条件断点发现条件为 false 之后下一个断点却停在了内层的 continue 行于是困惑了。实际情况是continue 被一个他没有注意到的“外层 if”或其他分支控制着并不是由那个 false 条件控制的。这种案例提醒我们断点排查时要把缩进、括号配对、所在 code block 的归属边界看仔细。IDEA 在光标所在行会有块级高亮显示所属括号利用这个功能把 continue 属于哪个 if 确认清楚再下结论不迟。我见过很多“灵异事件”最后都只是“人类视觉的盲区”。5.3 案例三Map 迭代时 get 带来的副作用还有一次是遍历 Map 时写了类似if (dataMap.get(key).size() 0)在 if 行断点时发现 key 对应的 value 是 null但程序还是走进了 continue。最后发现 if 条件里调用了computeIfAbsent这个调用在判断前往 Map 里放了一个新的集合Evaluate 窗口求值时又执行了一次 computeIfAbsent改变了 map 状态。所以说调试时调用的 getter/工具方法也有副作用尤其涉及并发容器时你看到的 Map 状态已经不同于判断瞬间的状态。遇到这种“带副作用”的条件表达式建议把方法调用部分改成先赋值给临时变量再比较既保证逻辑清晰也避免调试时重复求值导致状态漂移。代码可读性上去之后调试起来也不会一头雾水。5.4 案例四断点打在错误模块的同名类上项目里多模块、多个同名类非常常见。比如两个模块都定义了Utils你在一个模块的类里打了断点实际运行的却是另一个模块的同名类。断点在 Debug 启动时因为类名匹配也可能被加载到错误模块的类上。解决方案是在断点属性面板里核对完整限定类名、模块名或者用CtrlShiftF8打开断点列表把不需要的“同名类断点”去掉。这个问题在大型多模块项目里简直防不胜防。5.5 问题速查表现象最可能原因第一步排查动作断点命中 continue但 if 条件看起来 false有多个断点 / Resume 停下的是另一个断点打开断点列表清点断点条件断点“无条件”总是命中Condition 表达式求值抛异常退化命中查看 Debugger 控制台 evaluation failed命中的线程不对多线程/线程池都在执行同一段代码看线程名切换线程比较行号对不上源码字节码增强或旧 classRebuild Project 后重新 Debug变量值在断点处与判断时不一致if 判断里有副作用方法改成先存临时变量再判断断点属于别模块的同名类模块隔离没做对核对类名、模块名删除多余断点这张表基本覆盖了我遇到过的 90% 的“if 为 false 却走 continue”的情况。如果你按着表走完还没定位那大概率要深入业务代码本身——也可能是你引用的库的字节码太怪这时候就把断点换成日志断点用打印堆栈的方式追踪真实调用路径。我自己在调试这个问题上最大的体会不是“断点用得多就好”而是“断点用得少、但用得准才好”。当年第一次遇到“if 条件为 false 却停到 continue”时我也曾在变量面板里反复确认折腾了一下午最后发现不过是 continue 行残留了一个没条件的断点。从那以后我养成了三个习惯凡是需要长时间 Debug 的项目先把所有断点列表过一遍清除无关断点凡是条件断点的表达式先在 Evaluate 窗口求值验证一次凡是看不懂的路径直接用日志断点代替普通断点一次性收集完整执行轨迹。最后再分享一个小技巧如果你已经在 continue 行停住又想知道这个 continue 到底是不是由你看到的那条 if 分支触发的最快的方法不是猜测而是在该断点右键打开属性面板勾选“Log stack trace”并把 Suspend 暂时关闭然后 Resume看控制台打出的调用栈。栈帧里会明确显示当前执行点位于哪个方法、哪一层 if 分支比盯着 Variables 面板猜有效得多。希望这篇总结能帮你少走几趟弯路。