服务器告警弹窗跳出来的时候,才第一次注意到hprof是什么文件,磁盘空间莫名其妙被吃掉一大块,查日志的时候顺着文件后缀摸到这个东西。当时以为是日志组件生成的普通日志,直接批量删除,没多想背后的来由,直到后面应用又一次卡顿,运维同事过来,说删掉这个文件等于丢掉了定位内存问题的线索。
文件一般不会主动生成,大多是程序触发了内存溢出之后,虚拟机自动导出的,也能在启动参数里配置,让它在指定时机把堆里面所有对象的快照保存下来。文件体积浮动很大,我见过几十兆的,也碰过直接冲到十几G的,取决于应用当时加载了多少对象、堆内存设置多大。打开它不能用普通记事本,乱码铺满屏幕,试了系统自带的文本工具,加载半天直接卡死,后面才知道得用专门的分析工具去读里面的对象引用、占用大小这些信息。
有一次线上测试环境,频繁报OOM,自动生成了好几个hprof文件,磁盘使用率一路往上走,定时清理脚本没把这个类型加进去,没过多久就把分区撑满,连带其他服务写日志都失败。当时纠结很久,不知道是留着还是直接清,保留的话占空间,删掉又怕之后复现问题找不到依据。
这只是我的个人感受,不是标准答案。
后来慢慢摸清楚一点,它里面存的不是运行时的实时状态,是某一瞬间的静态快照,相当于给Java堆拍了一张照片。里面能看到哪些类实例数量异常多、哪些对象没有被正常回收,很多内存泄漏的排查,就是靠解析这类文件,顺着引用链找到一直没法释放的对象。但它也有局限,快照只记录触发那一刻的情况,程序后续的行为变化,在这个文件里完全看不到,单靠一个hprof,有时候根本没法锁定根因。
线上生产环境,很多人不会一直开启自动导出,一方面文件生成的时候会短暂阻塞应用进程,业务请求会出现停顿,另一方面超大文件传输、存储、解析都很耗资源。我自己试过在测试环境加上导出参数,复现问题的时候抓快照,抓完之后,先把文件拷贝到单独的存储目录,再把原文件清理掉,避免持续占用磁盘。
也踩过别的弯路,拿到文件之后,随便找个工具打开,不做筛选,密密麻麻的对象信息堆在眼前,越看越乱,分不清哪些是业务自定义对象,哪些是框架自带的基础对象,白白耗掉不少时间。之后慢慢养成习惯,先过滤业务相关的类,优先看实例数持续上涨的那部分,再去核对代码里的集合、缓存、线程池这些容易出现内存泄露的地方。
不是所有hprof文件都有分析价值。有时候只是一次短暂的内存峰值,不是持续的泄漏,就算解析完,也找不到需要修复的代码点,这种文件放久了只会占用存储,留着意义不大。区分有没有价值,没有固定标准,只能结合当时的报错日志、监控指标一起判断,单看文件本身很难下定论。
现在再碰到这个文件,不会第一时间动手删除,先看对应的时间戳,对齐监控面板,确认是不是和OOM告警同步生成的,确认有留存价值就归档,没有就直接清理。没有一套通用的处理方式,不同团队的存储配额、业务容忍度不一样,有人选择保留全部快照,有人只在问题上报之后才手动触发导出。
上次处理完那堆快照之后,没再给所有服务都加上自动导出配置,只在几个容易出内存问题的测试分支打开,生产侧依旧保持关闭,需要排查的时候,再临时手动触发抓取。