这个 Bug,给我整得一愣一愣!
Bug 年年有,今年特别多
大家好,我是鱼皮。
今年真不错,红红火火,一写代码就一片红,Bug 特别多!
这还没过多久,我就遇到了几个大 Bug。前段时间我刚上线的面试刷题网站(mianshiya.com)的 Bug 就不说了,这网站基本已经变成大家的靶场了。
今天给大家分享一个我在工作中遇到的 Bug,给我整得一愣一愣,给大家乐呵乐呵。
Bug 大赏之系统崩了
我在工作中负责的是 BI(商业智能)系统,大家可以简单理解为一个数据分析平台。
虽然平时 Bug 不多,但其实是因为用户太少了,很多 Bug 只是还没被发现而已。
这不,虽迟但到。那是一个风和日丽的上午,老板突然找到我说:整个系统崩了,用户看不到内容,你快查查!
然后我看了下日志,原来是 Java 后台程序发生了 OOM(Out Of Memory 内存溢出)!这可是个 Java 的经典老 Bug 了,而且面试官很喜欢问。
不过我和这位 Bug 已经很久没见,一时间竟有些陌生,也忘了从哪儿开始排查了,更不用说那些 JVM 常用参数和命令了。
不管怎样,我先把部分容器(程序运行的环境)进行重启,然后留一台用于排查分析。
先看日志,能看到一些 OOM 相关的报错,以及大概是哪个线程、哪段代码导致了 OOM:
但我特么对着日志提示的代码看了半天,也没发现哪里写的有问题啊!我一个 “专业” 的程序员,难道还看不出自己的代码有问题么?
好吧,既然日志看不出问题,那就老老实实走套路。通常遇到 OOM 内存溢出时,我们要 dump 堆内存进行分析。这 dump 内存可真是一件美事,由于内存占用很大,所以等了 40 多分钟才得到了 dump 文件。
为了便于对比分析,我还 dump 了两次不同状态的内存,每个文件 2 - 3 个 G!
然后可以用 JVisual VM 或者 Memory Analyzer Tool(MAT)之类的工具打开 dump 文件,看看到底是哪些对象占用了内存、有没有大对象没被回收掉导致内存慢慢占满等等。
仔细一看,基本内存都是被 List 列表对象占用了,列表项足足有一百万条,直接占了 1.6 GB!
但是这些对象都是 GC root Unreachable(没被引用)可回收的呀,一般用完就清理了,怎么会把内存撑爆呢?
我又用去线上容器中输入查看 GC 状态的命令进行分析,发现的确触发过几次 Full GC,回收过对象呀!
那到底为啥会 OOM 呢?
真相只有一个,因为同时处理的数据量太大,导致直接把内存挤爆了!
就像我们去丢垃圾,大家慢慢丢,然后等工人来回收垃圾、清空垃圾箱,之后我们再接着丢。虽然要等待回收,但起码不会爆。
但假如有个大垃圾想自己跳进垃圾箱里,结果垃圾箱装不下,那这个大垃圾就无可奈何了。
唉,这锅肯定是我来背了。最初申请容器资源时,没考虑到竟然会有这么大量的数据,只申请了 8 G 的内存空间。
不过按道理来说 1.6 G 不到 8 G 的 1 / 4,内存应该也不会爆?
别忘了,JVM 最大堆内存通常默认是系统内存的 1 / 4,正好是 2 G。再加上 JVM 分代回收,新生代一般只占堆内存的 1 / 3,老年代则是 2 / 3,如下图:
所以当一个超过老年代大小的对象要加载到内存时,新生代装不下,直接塞到老年代。发现老年代也装不下,会先触发 Full GC(垃圾呼吸 - 十一之型 - 全回收)。而如果 Full GC 后还是装不下这个对象,就 OOM 了,凉凉!
所以最简单粗暴的方式就是调整 JVM 最大堆内存参数,比如 -Xmx8192m,从而增大堆内存空间,装下大对象。
可以用 jmap 命令来查看 JVM 堆的参数,如下图,是我之前在 JDK 11 版本截取的一个示例:
JDK 9+ 的常用命令有变,新增了 jhsdb,要用到时上网查就好。
但这种方式只能解决燃眉之急,归根结底还是要看看到底是什么数据一次性占了 1.6 个 G!
仔细一看,List 里面装的都是 Map,每个 Map 表示数据库中的一行数据,总共 100 万行 15 列。
这,这不对吧,这些数据在数据库、Excel 文件里才不到 100 MB,咋变成 Map 直接膨胀了十几倍?
于是,我试着输出了一下 List<Map> 对象的大小,发现 105 行 * 10 列就占用了 155.9 KB!100万行的话真就 1.6 个 G 了!
那一刻,我突然想起了被八股文支配的恐惧,Java 的 HashMap 占用空间的确是很大的!每个 HashMap 对象包含 12 字节的 Java 对象头、4 个引用字段、3 个 int 型字段、1 个 float 字段,这些加起来是 44 字节。因为 JVM 要求对象内存大小必须是 8 字节的倍数,所以还要再补上 4 个字节,最终一个空的 HashMap 就占用了 48 字节。用它来存数据,可比其他格式的文件要大多了!
也是由于经验不足 + 太过自信了吧,我事先真没想到会有这么大的数据量,并且估错了数据对象占用的空间。
不过吃一堑长一智,下次再遇到类似问题(希望不会有下次),我应该就能很快解决啦。实践 + 翻车,印象真的太深刻了!所以建议大家在背八股文的同时,还是多多写代码做实验哦~
至于这个问题怎么解决。。那就别用 HashMap 呗!用 List + 下标来表示一行数据应该是可行的。当然,大家有更好的方案欢迎讨论~
以上就是本期分享,如果大家喜欢实践类的知识分享,求点个 赞 + 在看 吧,感谢大家 ❤️
往期推荐