记一次线上频繁GC

内存泄露Bug现场

线上某核心链路服务的一个节点疯狂GC,监控图以下:
正则表达式

平均1分钟触发CMS GC 36次,已没法正常处理线上请求。sql

准备工做

发现该节点有问题后,找运维将该节点从服务注册中心上摘掉,由于咱们须要去jmap dump服务的堆栈信息,而dump内存会STW,必须先摘流。缓存

dump命令以下:app

jmap -dump:format=b,file=heap.bin [pid]

dump好之后gzip压缩,便于文件传输到本地,从2G压缩到300+M左右。运维

分析

将dump的文件导入到MAT中,MAT内存分布图以下:
ide

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()方法的地方,代码以下:ui

@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撑爆了。spa

解决思路其实也很简单:在对性能没有极致要求的状况下,移除代码中对SQL的缓存;而直接走正则逻辑 而且 提早对正则表达式作好编译,多是更合理的选择。

总结

在没有极致性能要求的状况下,简化咱们的设计,服务可能会更具健壮性。

相关文章
相关标签/搜索