一次生产问题分析定位
记一次帮其它团队排查生产问题的过程,顺带结合该场景讲一下怎么用MAT分析内存,以及开发过程中的一些规范问题。由于重启前没有用jstack、jmap命令保存jvm的内存信息,应用的日志打印也不规范,导致排查比较艰难。
问题定位过程
问题发生时间:
- a. 下午发现生产大量交易超时,排查发现依赖的一个被很多交易依赖的下游接口超时
- b. 服务重启后问题暂时恢复,但当天未排查到原因
- c. 第二天参与排查
排查定位过程:
-
a. ELK上看不到该应用日志,团队也说没拿到日志。先是团队反馈应用日志报错拿不到“数据库连接”,截图只有如下内容(后续看到这个截图截了一半有点害人)。
:Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool由于我们应用并发并不高,第一反应以为是有长耗时Sql或者是大事务导致链接无法释放,最终链接不够用。检查代码,发现用到事务的代码只有一个地方,且只有简单的插入、更新操作,DBA反馈当时数据库也没有慢Sql。问题一时有点无头绪
-
b. 拿到应用侧出错时间段日志后,搜索Exception关键字,发现一小时有上千条异常日志,对异常排查,未发现会引起连接池不够的异常(异常太多,我也没仔细检查,导致重要异常被忽略。后续了解这些异常正常交易时间段也会报,应该用warn或者info级别)。
-
c. 查看其中CannotGetJdbcConnectionException异常,查看报错:
:Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 17276, active 7, maxActive 200, creating 1, createElapseMillis 17795其中有active 7,说明不是链接池满了,而是链接不够用无法创建新链接。和DBA确认当时数据库无异常,检查链接数也无问题;检查linux服务器涉及线程数的配置也都足够。由于当时重启时未拿到jvm信息,暂时也没了方向(其实无法创建链接,这时应该怀疑jvm内存问题,可以检查一下是否有内存溢出问题)
-
d. 经检查,发现日志中有"java.lang.OutOfMemoryError: Java heap space"报错。启动脚本配置有内存溢出则自动保存内存堆栈,检查机器上确实有堆栈文件。这样问题基本清晰,就剩下分析该文件了定位内存溢出原因了
内存溢出问题分析
我用的是MAT工具,其中的一些参数,Shallow Heap:浅堆,指一个对象所消耗的内存(不包括内部引用的对象大小);Retained Heap:深堆,某对象保留集中所有对象的浅堆之和;outgoing reference:当前对象引用的外部对象集合;incoming references:引用当前对象的外部对象集合。
-
a.首先Leak Suspects页面,有可能存在的内存泄漏。代码质量问题,有很多同名线程池,无法定位问题。
-
b. Histogram视图。能看到占用内存最大的是char[]对象
-
c. 点击char[]->右键->List objects->with incoming references。按照Reatained Heap排序。这时可以看到占内存大的char数组中的具体信息,根据字符串格式、类名可以定位问题。(能看到一部分是日志,一部分是对象的序列化)
-
d. dominator_tree,查看大对象。同样看到线程里都是打印日志的字符串
现在基本可以定位是日志打印大对象,导致内存溢出,无法创建新的数据库连接,服务不可用。根据日志字符串中的类名,在idea检索引用,最终查到是一个2万大小的对象数组在多个线程中log打印序列化,导致了该问题。
总结
通过该问题排查过程,可以看到,在开发及碰到生产问题后,以下几点有助于我们快速定位问题
- a. 为快速恢复生产,重启前通过jstack、jmap等命令打印jvm内存信息
- b. 规范error日志,只有真正需要排查的问题才能是error级别
- c. 线程池的命名应该易辨识