Jar包会自己消失?Excel会"记忆"数据?我遇到了两个灵异bug
这两个bug放在我的职业生涯中也算是相当炸裂的存在了
这周可能是运道不佳,短短几天,接连遇到两个我认为完全"不可能"出现的Bug,当然从最后的结果反推,这两个问题可谓是相当的反直觉,甚至最后破案的关键辅助还离不开一点幸运的偶然因素🤣
两个bug,一个是java应用启动之后,过一段时间莫名的包各种ClassNoDefFoundError;另外一个则是上传excel模板后端解析模板数据返回给前端的场景,后端总是返回一些excel中没有的数据,简直是灵异事件了
这两个问题都花费不少时间进行排查
现在,我想记录一下这两个灵异bug的排查过程。当然本文并不会是技术类教程,权当成一次社畜的技术侦探过程记录吧。
问题一:Jar包的神秘消失(根因)
现象
项目启动一切正常,运行一段时间之后,忽然有小伙伴反馈服务大量超时不可用,查看日志之后发现挺多NoClassDefFoundError。
起初的报错是业务类,解决依赖冲突后,问题依旧。接着开始报SpringMVC框架类缺失,然后是各种第三方库的类找不到。

第一层排查:依赖冲突?
看到这个问题,很容易想到的就是依赖冲突了,哪个报错解决哪个即可,然后我就开始做一些标准检查操作:
- Maven依赖树分析 ✅
- 重复Jar包检查 ✅
- 类加载器调试 ✅
修了几个冲突的jar包,重新发布之后发现并没有什么鸟用;
直到我看到一个关于Spring MVC的ModelAndViewXxx也出现同样的异常,基本上可以排除,不会是版本冲突的问题了~(这么核心的依赖,当然只会有一个版本)

又怀疑是不是什么类在初始化的时候抛了异常(比如static初始化区域抛异常,导致这个类无法正常使用,后续再使用它的时候也会报NoClassDefFoundError),但是一般这种情况还会伴随有ExceptionInInitializerError,显然也不是这个问题
第二层排查:进容器查看
上面的常规分析无果之后,我就准备到POD节点上,解压一下jar包看看里面是不是生成的fat jar包有问题,结果发现无法进入pod内,在进入shell时,提示

具体的提示如下:
sh
error executing command in container: failed to exec in container: failed to start exec "173a6a4376b81821ad476313c942a3835a353a0295cd0af8bc99046efdb6626f": OCI runtime exec failed: exec failed: unable to start container process: chdir to cwd ("/data1/www/release/我的服务名") set in config.json failed: no such file or directory: unknownInternal error occurred: error executing command in container: failed to exec in container: failed to start exec "b5b90530877378b4a0cd52871d53721d11477884d84929422e4756de6053db2c": OCI runtime exec failed: exec failed: unable to start container process: chdir to cwd ("/data1/www/release/我的服务名") set in config.json failed: no such file or directory: unknownUnable to open a shell to the container (none of the shell commmands succeeded)
好像找到根因了,上面这个提示表示
容器还能活着,但它的 WORKDIR 目录已经不存在了
所以 docker exec 想进容器时,chdir 失败,直接拒绝启动 shell。
这个表明啥?
工作目录都没了,那我的jar包也没了啊 ------------ 进程虽然在,但是jar包被删了,应用还能正常服务吗?!!
第三层排查:部署环境?
究竟是谁,删除了我的jar包;作为一名合格的研发,出现这个问题显然第一责任人就是------------运维(没错,肯定是你的锅 )
然后怀疑点顺理成章的转向部署环节:
问1:打包脚本有没有问题? ❌ 否
答:同样的打包脚本,一两年都没人动过了,不像是它的问题;而且别的应用都没有问题就你这个不对,通过同类对比,不是它的锅
问2:Pod存储卷有问题?❌ 否
答:启动多个实例,分散不同的宿主机,这个问题依然没有避免,所以也不是这个原因
问3:宿主机的清理脚本误删了文件?❓存疑
排除所有不可能的原因,那么问题就是你了!!!
好吧,这个时候运维大佬出马,排查所有的清除任务,明确的说,不是我的锅,没有这样的任务 🤷♀️
为了不影响业务正常运转,项目启动之后,我先直接进入pod内的工作空间,保持这个登录的常驻,这样即便有脚本要删除文件,也会因为文件句柄被持有而无法正常执行
第四层排查:时间线分析
我们开始整理异常出现的规律与时间线
step1: 服务启动
step2: 服务正常响应
step3: 第一次NoClassDefFoundError
step4: 大量第三方类报错
项目启动之后,什么时候会报错这个不确定,但是从第一个错误出现之后,接下来就是大量的ClassNoDef异常了
这个删除动作,还有点随机的感觉,基本上不可能是打包的问题出现的;那么一定是有个任务,在执行删除动作,现在的难点就是找出这个"真凶"
怎么办?老实的回溯日志,看看这个第一次错误出现之前,究竟干了些啥
真相大白:临时文件清理的代价
首先翻一下日志的异常情况,发现一个可疑点

在jar包的工作目录上加载excel文件,报了一个FileNotFoundException,这个异常没什么特殊的(工作空间都被删了,当然会找不到了),关键点在于工作目录中为什么会有这个excel文件,这就有点奇怪了呀
然后根据堆栈,找一下代码实现,在代码中,发现了有删除文件的实现
java
if (file.exists()) {
cn.hutool.core.io.FileUtil.del(file);
}
整个这段逻辑,是包裹在一个异步任务导出的逻辑中,产品的诉求是将一批导出的excel文件,最后压缩到一个zip包返回给用户,因此研发的小伙伴采取的动作是先讲文件都导出到本机,然后压缩之后上传到oss返回给用户;为了避免临时文件过多,就写了一个清除临时文件的回收逻辑;
这个思路很好,但是问题在于:
- 这个临时目录选择有问题呀,直接将项目工作路径作为临时文件存储目录
- 清除临时文件时没有直接将工作目录给干没了,jar包也被误删
分析到这里,后面的确认(找到入口触发一下,看下是不是jar被删了)和修复动作就好说了
所以这个bug不是Jar包出现"灵异的消失",而是被系统错误删除了🤣
话说工作这么多年,这还是第一次遇到把jar包给干删除的bug,发现这个原因时,整个人都是懵的~
问题二:Excel的"记忆"功能
上面这个问题解决完第二天,又出现一个更诡异的bug,一个后台解析excel的代码实现中,居然返回了excel中没有的数据,这个现象简直比AI还幻觉了~
现象
Excel模板导入解析功能,有一个诡异的现象:

- 使用原模板(24年)导入 → 正常解析为24年
- 修改模板第一列为26年 → 解析结果还有24年数据
- 重启服务、清除缓存、换机器测试 → 还是24年
第一层怀疑:解析器缓存
首先怀疑FastExcel有缓存机制:
java
public List<YearResponseDTO> parseProjectIncomeExpense(MultipartFile file) {
try (InputStream inputStream = file.getInputStream()) {
ExpenditureListener listener = new ExpenditureListener();
EasyExcel.read(inputStream, YearResponseDTO.class, listener)
.sheet("博文发表记录")
.headRowNumber(7)
.autoTrim(true)
.doRead();
return listener.getData();
} catch (Exception e) {
return Collections.emptyList();
}
}
检查了所有配置,甚至看了部分源码,没有发现缓存逻辑,因此缓存问题:❌ 否。
当然从实现上看,表头的行数这里有个问题,代码实现认为开始7行为表头,但是上传的excel只有5行是表头,当然从实现来看,多排除了两行,只会出现数据变少,怎么会出现显示不存在的年份数据呢?pass
第二层怀疑:并发问题
怀疑多线程上下文共享的问题,比如初始模板的上下文没有被清除,导致被后续使用了
这个问题也很容易证伪,因为我重启项目之后,再次解析修改后的excel文件,特么的居然还是有问题
问题依旧。
第三层怀疑:文件本身
开始怀疑Excel文件有隐藏问题:
- 隐藏行/列? ❌ 否
- 隐藏表格? ❌ 否
- 读取了错误的sheet页? ❌ 否
说实话,直觉已经明确告诉我,肯定是这个excel文件的问题,但是关键点是我找不到证据
咋办?
把这个表格内容复制出来,重新建一个空白的excel文件试试,结构发现依然有问题!!!
然后就开启劳工模式,debug到FastExcel的源码实现层,然后将解析的数据与excel的数据进行对比,发现除了年份不对,其他的行数据都能对上,这就很搞人了
一度准备升级依赖版本了,因为我们用的还是2.x,而最新的已经是4.x了,但是升级居然不兼容,蒜鸟蒜鸟,放弃升级,继续找可能性😭
真相大白:合并单元格的陷阱
一个偶然的操作(或者说灵机一动),将excel模板中年份的合并单元格取消,结果神奇的一幕发生了

合并前: A6:2024 A7:2024 A8:2024 ... A14:2024
合并后: [A6:A14] 显示2024
当我们在Excel中修改时:
- 点击合并单元格,输入"2026"
- Excel只修改了第一个单元格(A6)
- 其他单元格(A7-A14)保持原值
- 合并后显示为2026,但实际数据没变
FastExcel读取时,会读取每个单元格的实际值:
- A6 → 2026
- A7 → 2024
- A8 → 2024
- ...
还记得上面源码中表头的地方吗,认为前面7行是表头,然后就顺利的丢掉了A6,A7这两行,所以返回的结果中没有2026年的数据;后面的A7-A14读取的还是原来的2024,所以就出现了这种 "所见不所得" 的灵异现象了
不得不说,这个问题深刻的反映了一个哲学观点:有时候眼睛是会骗人的,你看到的也不一定是真实的🤣
小结
这两个bug都非常灵异、也很不典型,在我的整个职业生涯中,也算是很炸裂的存在了。
从事后来复盘一下整个问题的定位过程,发现还是有一些可供讨论的侦查思维 (说实话,这两个bug的定位过程,还真有点像侦探破案的感觉)
思维模型一:时间线分析
"当问题看起来随机时,给它加上时间维度。"
NoClassDefFoundError问题的突破点,就是发现错误是逐步扩散的。
建立时间线的三个步骤:
- 收集所有相关日志,按时间排序
- 标记第一次异常出现的时间点
- 观察异常扩散的模式(从核心到边缘?从业务到框架?)
思维模型二:环境差异分析
"如果在A环境正常,在B环境异常,差异点就是线索。"
Excel问题的关键,是发现修改方式不同,结果不同:
- 直接修改合并单元格 → 问题出现
- 取消合并后修改 → 问题消失
对比实验的四个维度:
- 操作顺序差异(先合并vs先修改)
- 工具差异(Excel vs WPS vs 程序生成)
- 数据规模差异(小文件vs大文件)
- 环境差异(本地vs测试vs生产)
思维模型三:最小可复现案例
"如果你不能稳定复现,你就不能真正理解问题。"
这两个问题都经历了从偶发到稳定复现的过程。
构建复现案例的三个要点:
- 剥离所有无关因素(其他业务逻辑、网络、并发)
- 从最简单的情况开始(单线程、最小数据集)
- 逐步添加复杂度,直到问题出现
思维模型四:怀疑自己的假设
"最危险的Bug,往往藏在你认为'不可能'的地方。"
我们最初的两个错误假设:
- "Jar包不可能被删除" → 结果就是被删了
- "Excel修改后数据肯定变了" → 结果就是没变
最后的思考题
这两个问题有一个共同点:都是"正确"代码产生了"错误"结果。
清理临时文件是正确做法,但路径错了。
修改Excel单元格是正确操作,但合并单元格的特性导致了问题。
这让我想到技术债务的真正成本:
"不是写错的代码,而是写'对'的代码在错误上下文中运行。"
所以,我的最后一个问题是:
你现在维护的系统里,有哪些'正确'的代码,可能在将来某个意想不到的时刻,产生'错误'的结果?
不必急于回答。让这个问题在你脑子里待一会儿。
有时候,好问题比好答案更有价值。
推荐大模型应用开发精选博文