公元20几几年的某一天,笔者正在公司清闲地啃着早餐,手机忽然传来一个应用告警!!!打开一看: 应用老年代内存使用率超过95% 几个大字赫然映入眼帘,吓得笔者赶忙丢下了手中的肉包子,赶忙排查!java
下图是后面解决了问题后从应用监控中补拍的做案证据,明显看到在一段时间内应用的老年代在频繁进行着full gc,且内存高点一直在100%处徘徊。sql
咳咳,做为一个成熟的java攻城狮,在面对着oom这种小毛贼时,笔者天然是保持着一脸淡定(其实心里慌得一批,毕竟当时刚入职没两天,怎么就接了这么个锅)。数组
当时第一反应是先咨询了运维同事,发现监控平台上并无集成内存分析的工具,须要自行dump分析。既然如此,那只能硬着头皮上了,笔者二话不说接入跳板机,登陆生产环境,一招 jps 找到当前java应用的进程pid,又一招 jmap -dump:format=b,file=filename pid , 内心想着等老子拿到dump文件,必定好好修理你个凶手。时间漫长地过去了 ~~~ 10分钟,终于拿到了dump文件(此处省略各类与生产环境文件导出的斗智斗勇)。缓存
笔者火烧眉毛拿出左右两柄手术刀(java自带的 visualvm 与一个比较有名的外部分析软件 mat),准备对dump文件进行尸检。毕竟第一次使刀,握着刀柄一时半会儿不知如何下手,真是丢我攻城狮的脸!折腾了半天,终于打开了 mat,载入了dump文件。(mac使用mat有时可能遇到启动失败的坑,此处不做赘述,请自行 度娘/google)框架
划开尸体的第一刀,映入眼帘的是血淋淋的内脏布局,以下图:运维
拉到accumulate objects in dominator tree项,它为咱们列出了这个asyncAppender对象的内存结构。果真如咱们所料,logback框架会使用一个队列来缓存一个个loggingEvent对象,每一次调用日志输出都会被包装成一个loggingEvent缓存起来。从上图第1、二点咱们看到这个日志缓存队列占据了十分巨大的内存,而且他的loggingEvent对象光一个就占了23%左右的内存,个人天。上图第三点的inspector观察窗口能展现出这个对象的具体内容,因而咱们截取了前两个占比最大的loggingEvent对象的msg来看看这个日志的内容究竟是啥?dom
区区oom果真没能逃过笔者的法眼,案件最后定性缘由以下:因为sql的设计错误致使批量查询数据量巨大,超过了dubbo的传输限制,于是抛出异常并写日志。而日志对象中包含了巨量的查询返回对象具体信息,并丢进异步日志队列,日志还没来得及刷新到磁盘就已经撑爆了内存,oom发生告警!! 后续优化的时候除了修改sql,优化了批量查询的条件,同时还在logback配置上加入了日志最大长度的限制,杜绝因日志打印致使的oom再次发生。异步