記一次線上頻繁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的緩存;而直接走正則邏輯 而且 提早對正則表達式作好編譯,多是更合理的選擇。

總結

在沒有極致性能要求的狀況下,簡化咱們的設計,服務可能會更具健壯性。

相關文章
相關標籤/搜索