記一次經過Memory Analyzer分析內存泄漏的解決過程

情況描述:

最近項目新打的版本,過不了多長時間,項目就會掛掉。情況就是處於一種假死的狀態。索引查詢都很慢,幾乎進行不了任何操做,慢慢卡死。
而後咱們再發版時,只能基於以前打好的war包,替換或者增長class文件。mysql

狀況對比及分析:

因爲以前代碼作過一次大整頓,提交的代碼比較多,因此經過回滾版本的方式解決,比較困難。一是由於整頓的成果不能白白抹殺;二是那麼多文件,靠人工挨個對比查找,比較困難。sql

解決方案一:

以前, 一直對目前項目的打包方式心存質疑,因此此次發生問題時,我首先懷疑的對象是Jenkins和生產的Tomcat服務器。我經過堡壘機鏈接到生產時,發現經過Jenkins啓動應用程序,會啓動兩個兩個tomcat進程。數據庫

而後,這彷佛更加堅決了個人見解,立刻就找到了運維,可是經確認後,是沒有問題的。一個是用root用戶啓動的,一個是用tomcat用戶啓動的。一個守護進程,一個應用進程。tomcat

解決方案二:

排除了服務器的問題,開始正面考慮程序的問題。
從新發項目有問題的版本,Dump下來的日誌,而後迅速回滾觀察。單臺機器的dump日誌有5個G:服務器

經過Memory Analyzer分析,在Leak Supects Report 視圖中,有以下分析結果:運維

上圖所示,共有三類問題a、b、c;還有一些其餘的,類型爲d。dom

先來看第一個問題(後來發現,前幾個問題都是同一個問題)spa

先點開Details看一下:線程

上圖顯示了一個很明顯的有問題的線程:地址是0x7c8ff3df0 ,名稱爲pool-16-thread-1。
經過《Accumulated Objects in Dominator Tree》視圖能夠看出,在該線程中,存在一個大的List對象,List對象內存放了大量的mysql的jdbc對象。3d

咱們想看看JDBC對象裏面堆放了哪些數據。接下來咱們打開《open dominator tree for entire heap》這個視圖:

找到名爲0x7c8ff3df0 pool-16-thread-1的線程。如圖也能發現,這個線程佔用了大量的空間未釋放。一層層打開裏面的存放的對象:

這裏的數據,是咱們的一張用戶表的數據。因此這就能夠得出結論:一個線程內,一個list內存放了大量從數據庫中獲取的用戶對象!
想到這裏,咱們又去看了b、c的問題描述,也是一樣的問題。估計是在不一樣時間點,經過gc已經回收了部分。

而後,我剛纔看了a問題的details信息,接下來咱們看下a的stacktrace 堆棧信息。

如上圖,問題就很明顯了,在Service的112行中,調用的findByCustomerID方法中,有掃描全表的操做。通過分析,找到對應的位置,對應的代碼爲:

customerID = StringUtils.isNotBlank(customerID)?customer.getCustomer().getCustomerID():null;

                        Customer oldCutsomer = customerService.findByCustomerID(customerID);

顯而易見,流程走到這裏時,customerID永遠爲空,那麼customerService.findByCustomerID(customerID)方法,會執行掃描全表操做。因爲該表數據量巨大,開發所認爲的用戶每次執行的索引查詢,實際上都成了慢查詢,並且須要返回全表數據。大量線程過來,佔用大量數據庫鏈接,致使數據庫鏈接數不夠;而每一個線程處理時,須要大量時間,  致使項目處於一種假死的狀態。

總結分析:

一、在工做中必定要規範代碼管理,才能提高開發效率,下降企業遭受損失的風險;

二、經過此次實踐發現,目前開發權限小,致使跨部門協同效率不是很高,接下來的開發中,立項之初就創建項目開發流程,從而提高開發效率;

三、解決問題的方法遇到瓶頸,嘗試第二種方法,多角度多層次對問題進行突破。

做者:劉正權
相關文章
相關標籤/搜索