前言
其实,这篇文章早在 年就完成了初稿,后面一直没来得及完善(各种加班各种忙),所以一直没来得及整理发布。而且,我从这个案例里学到的东西太多了,很多内容并没有体现在本篇文章中,后续有机会一定会再写文章分享。话不多说,一起来看正文吧。
缘起
前一阵子,有朋友在微信上发了一段 的输出信息,大概内容如下:
This dump file has an exception of interest stored in it. The stored exception information can be accessed via .ecxr. (fd0.9bc): Security check failure or stack buffer overrun – code c0000409 (first/second chance not available) For analysis of this file, run !analyze -v
… 省略 N 行
看样子像栈相关的问题。于是简单跟朋友说了下我的猜想,有可能是栈破坏了。没想到,远不是这么简单。
说明: 之所以一定要写这篇总结,是因为我在分析过程中犯了很多想当然的错误,记录下来,以后不要再犯。
另有隐情
过了一会,朋友发了一段更贴近真相的错误提示,并且发送了更详细的语音描述。
这个提示跟最开始的提示风马牛不相及。一个是栈相关的问题,一个是内存分配的问题。
说明: 经常分析转储文件的小伙伴儿应该都知道,直接打开转储文件时, 给出的提示有可能是不准确的,如果想获取最准确的信息,最好通过 切换上下文,然后再执行 系列命令查看。但是,很多时候直接执行 就能拿到正确信息。正式分析之前,不妨先试试 。
又跟朋友又聊了几个相关问题,得到了更多的关键信息。比如,
-
之前解决过类似的内存泄漏问题,当时抓的 有 多。
-
这次抓取的 只有 ,但是确实是 。
说明: 内存相关问题,最好抓一个 ,否则分析到一半,由于转储文件缺少关键信息,没法继续确认,就太尴尬了。
-
程序是 位的,并且开启了大地址。
说明: 位程序的虚拟地址空间比 位程序大多了,不太容易在短时间内看出问题。
-
前几次出问题时都是运行了很久才出问题,这次只运行了 个小时左右就出问题了。
-
几次出问题时,都是在内存比较紧张的时候。
聊了一会,问朋友是否方便发送完整的 结果。没想到,朋友除了发送 的分析结果外,还特别贴心的发送了 转储文件和对应的符 文件。必须为朋友点赞,看来没少分析转储文件。
查看分析结果
由于当时在北京出差的路上,于是在地铁里用手机查看了一下 的分析结果(旁边的小哥哥小姐姐会不会以为我在看小说,跟朋友说的一样,打开转储文件后, 给出的错误提示确实是栈相关的,但是 却指向了内存分配相关的问题。
这里简单摘录几个关键的信息:
-
转储文件中的
说明此 是注册为 的 在程序异常的时候抓取的。
-
运行环境
服务器系统,系统运行时间是 ,程序运行时间是 。跟朋友的描述是一致的。
-
上下文相关信息
可以使用 切换上下文,使用 查看异常信息。
-
调用栈
说明: 看到 时,脑子里自然而然地想到了两种可能:
-
分配的内存超级大(本例不属于这种情况)
-
内存不够用了,没有一块内存可以满足本次的分配请求。
之前遇到的 异常都是尝试分配的内存超级大(非常典型的是在反序列化 , 的时候,由于内存错位导致尝试分配超大内存),但是本次的分配请求看上去相对合理。
既然是跟内存分配相关的问题(而且是标准库中涉及到的内存分配),很可能跟堆有关,可以查看堆相关的信息。在 中执行 命令,查看堆概要信息。
-
难道没有直接查找空闲堆块的命令吗/p>
有插件可以做这事吗/p>
自己写个脚本(程序)整理下输出结果/p>
想个办法过滤一下空闲块,然后再排序/p>
最后一个想法简单易行:把所有包含 的行找出来,然后保存为 文件。按 前面的字段(当前堆块的大小)降序排列,就可以很快找出最大空闲堆块了。
整个过程参考下面视频:
最后发现最大空闲堆块的大小是 ,比 要小。
意外发现
在查找过程中偶然发现了一个有意思的现象,同一个地址会出现两次,比如 这个地址:
原来可以通过 直接显示所有的空闲堆块。于是在 中输入 验证一下,果然可以列出所有空闲堆块。
说明: 空闲列表中的最大空闲块的尺寸也是 。前面做了这么多无用功,真是浪费时间。
既然请求大小超出了阈值,接下来的任务是找到整个内存空间中最大空闲区域,看看其是否能满足本次分配请求(应该是不满足,要不然也不会抛出异常了)。
查找空闲区域
相信,很多小伙伴儿都知道使用 查看地址信息。我经常在排查内存访问异常的时候,通过该命令查看某个地址的详细信息。比如,下图是通过 查看到的关于地址 的相关信息:
内存严重碎片化
说实话,刚开始我是有点不敢相信 给出的结果:最大的空闲空间居然只有区区的 !
继续使用 命令打印出所有的空闲内存。整理后的结果如下图所示:
表示以逗 分隔的 格式输出,这样可以把结果直接粘贴到 文件中。
view-largest-free-region-in-process 可以看出,跟 给出的结论是一致的。最大空闲区域的大小是 (十六进制对应的数值是 )。空闲内存的总大小是 ,只有区区的 (也就是 )。碎片化真的是太严重了。
再回过头仔细看 的输出结果。其中有如下提示:
意思是说虚拟地址空间的碎片率已经高达 。看来这次的崩溃确实是由内存碎片导致的。
总结
之前只是理论上知道如果碎片太严重可能导致分配失败,没想到这次真遇到了。刚开始给出的结论虽然正确,但是理由太牵强,站不住脚。
-
使用 分析转储文件,先执行 ,很多情况下问题就解决了。
-
发生异常的时候会保存线程上下文和异常相关信息。异常信息可以通过 查看,线程上下文可以通过 查看并切换。
-
在 中可以通过 查看对应命令的帮助。
-
中的 扩展命令是查看堆信息的好帮手, 可以给出堆的概要信息。 可以显示所有空闲堆块, 可以显示所有堆块。
-
命令相当强大。 可以查看指定内存地址的信息, 可以给出一个概要信息, 可以给出特定的内存区域,甚至后面还可以跟命令。
-
堆中分配内存时,如果堆大小可以增长并且申请的大小超出阈值(),会直接使用 进行分配。
参考资料
《软件调试》第一版
帮助文档
未完待续
后面又跟朋友折腾了一些其它问题,颇有收获,希望能有时间做个总结。
-
为什么 位程序可以使用的内存空间是 应该是系统使用的才对户态代码也可以使用/p>
如果一条命令输出结果太多,直接显示的话太慢,怎么办/p>
转储文件中包含了一些珍贵的数据(比如某些充值记录),如何找出来/p>
-
…
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91437 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!
-