共计 1424 个字符,预计需要花费 4 分钟才能阅读完成。
内存泄露 Bug 现场
线上某外围链路服务的一个节点疯狂 GC,监控图如下:
均匀 1 分钟触发 CMS GC 36 次,已无奈失常解决线上申请。
筹备工作
发现该节点有问题后,找运维将该节点从服务注册核心上 摘掉
,因为咱们须要去 jmap dump 服务的堆栈信息,而 dump 内存会STW
,必须先摘流。
dump 命令如下:
jmap -dump:format=b,file=heap.bin [pid]
dump 好当前 gzip 压缩,便于文件传输到本地,从 2G 压缩到 300+ M 左右。
剖析
将 dump 的文件导入到 MAT 中,MAT 内存分布图如下:
1.2G 的内存都被 AccountChangeTask 中的 ConcurrentHashMap 对象占用了,那思路就很清晰了,去查看代码中什么中央应用了 这个 AccountChangeTask 对象。
AccountChangeTask 的整体构造如下:
@Service
public class AccountChangeTask {
// 缓存 SQL 和 tableName 映射关系
private static final Map<String, String> sqlMap = new ConcurrentHashMap<>();
@Async
public void processTask(String sql) {
// 对 sqlMap 对象的 get put 操作,key 是 SQL,value 是表名
// 起因是逻辑中有一些对 SQL 做正则解析的操作,可能比拟耗时和耗 CPU,所以想通过缓存优化
... 其余业务逻辑
}
}
咱们再去查找应用了 AccountChangeTask.processTask()办法的中央,代码如下:
@Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
@Component
public class MybatisInterceptor implements Interceptor {
@Autowired
private AccountChangeTask accountChangeTask;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 省略...
String sql = showSql(config,boundSql) // 填充为实在的 SQL,将? 填充为实在的 SQL 参数
// 业务逻辑判断,如果 true 走上面逻辑
accountChangeTask.processTask(sql);
}
}
问题解决
剖析代码,起因就是缓存在 ConcurrentHashMap 中的 SQL 是被参数填充过的 SQL,而线上环境的 sql 参数变幻无穷,有不同 uid 和工夫等等,申请量一上来就把 ConcurrentHashMap 撑爆了。
解决思路其实也很简略:在对性能没有极致要求的状况下,移除代码中对 SQL 的缓存;而间接走正则逻辑 并且 提前对正则表达式做好编译,可能是更正当的抉择。
总结
在没有极致性能要求的状况下,简化咱们的设计,服务可能会更具健壮性。
正文完