当前位置: 首页 > 后端技术 > Java

记得一个频繁在线GC

时间:2023-04-01 22:55:16 Java

内存泄漏的Bug。站点某核心链接服务节点疯狂GC。监控画面如下:平均1分钟触发CMSGC36次,无法正常处理在线请求。前期准备工作发现节点有问题后,我们要求运维将该节点从服务注册中心移除,因为我们需要去jmapdump服务的栈信息,dump内存会STW,所以必须先删除流。dump命令如下:jmap-dump:format=b,file=heap.bin[pid]dump完成后会进行gzip压缩,方便文件传输到本地,从2G开始压缩到大约300+M。分析将转储文件导入MAT。MAT内存分布图如下:1.2G内存被AccountChangeTask中的ConcurrentHashMap对象占用,思路很清晰。检查代码中使用AccountChangeTask对象的位置。AccountChangeTask整体结构如下:@ServicepublicclassAccountChangeTask{//缓存SQL与tableName映射关系privatestaticfinalMapsqlMap=newConcurrentHashMap<>();@AsyncpublicvoidprocessTask(Stringsql){//是sqlMap对象的getput操作,key是sql,value是表名//原因是sql中有一些常规的对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})})@ComponentpublicclassMybatisInterceptorimplementsInterceptor{@AutowiredprivateAccountChangeTaskaccountChangeTask;@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{//省略...Stringsql=showSql(config,boundSql)//填充真正的SQL,填充?withrealSQLparameters//业务逻辑判断,如果为真,则进入下面的逻辑accountChangeTask.processTask(sql);}}问题解决分析代码,原因是缓存在ConcurrentHashMap中的SQL填充了参数,线上环境的sql参数千变万化,不同的uid和时间等,一旦请求量上来,ConcurrentHashMap就会爆炸。解决方法其实很简单:对性能没有极端要求的情况下,去掉代码中SQL的缓存;直接按照正则逻辑,提前编译好正则表达式,或许是更合理的选择。总结在没有极端性能需求的情况下,简化我们的设计,服务可能会更加健壮。