一次線上內(nèi)存泄漏的解決過程

內(nèi)存泄漏這種問題是可遇不可求的經(jīng)歷历极,終于有機(jī)會(huì)抓住了它窄瘟,要好好的記錄下來。出現(xiàn)問題的是打成jar包的一個(gè)引擎程序

引擎邏輯

大致是生產(chǎn)者消費(fèi)者模式的一個(gè)數(shù)據(jù)處理引擎

public class MainClass {
    public static void main(String[] args) {
        try {
            //定義 線程池趟卸、隊(duì)列蹄葱、門閂
            ExecutorService service = Executors.newCachedThreadPool();
            BlockingQueue<JSONObject> queue = new LinkedBlockingQueue<JSONObject>(100);
            CountDownLatch latch = new CountDownLatch(10);
            //1個(gè)生產(chǎn)者
            Producer producer = new Producer(queue);
            service.execute(producer);
            //10個(gè)消費(fèi)者,每個(gè)消費(fèi)者加門閂锄列,消費(fèi)完成減一
            for (int i = 0; i < 10; i++) {
                service.submit(new Consumer(queue,latch));
            }

            service.shutdown();
            //主線程等待門閂图云,都完成后開始第二次循環(huán)
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
        }catch (Exception e){
        }
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("循環(huán)一次結(jié)束,第二次開始調(diào)用");
        main(new String[]{});
    }
}

業(yè)務(wù)邏輯為生產(chǎn)者消費(fèi)者啟動(dòng)邻邮,用CountDownLatch來阻塞住主線程竣况,等所有消費(fèi)者生產(chǎn)者線程完成并結(jié)束后,main方法開始調(diào)用自己筒严,開始第二次啟動(dòng)丹泉,循環(huán)調(diào)用

這種情況下運(yùn)行一段時(shí)間后會(huì)出現(xiàn)異常:

Caused by: java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:957)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1367)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)

針對(duì)OutOfMemoryError異常我們使用jdk自帶的工具jvisualvm來查看

jvisualvm使用

jvisualvm自從 JDK 6 Update 7 以后已經(jīng)作為JDK 的一部分,位于 JDK 根目錄的 bin 文件夾下鸭蛙,無需安裝摹恨,直接運(yùn)行即可

image.png

打開后左側(cè)是所有的進(jìn)程,可以打開任意一個(gè)進(jìn)行詳細(xì)信息查看
image.png

右側(cè)對(duì)應(yīng)顯示詳細(xì)信息
image.png

分析程序崩潰時(shí)堆文件

程序運(yùn)行時(shí)娶视,設(shè)置參數(shù)

-Xms200m
-Xmx200m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/dump/

設(shè)置最大內(nèi)存和指定OutOfMemoryError時(shí)存儲(chǔ)堆文件的位置

我們使用jvisualvm打開堆文件java_pid42132.hprof


image.png

占用內(nèi)存最大的是Obeject[]和byte[]晒哄,并沒有顯示具體是哪個(gè)類導(dǎo)致的內(nèi)存問題,暫時(shí)無從下手。

猜想1:線程池的線程數(shù)過多導(dǎo)致

我們只能從程序邏輯來猜想這個(gè)問題了揩晴,由于程序多次回調(diào)勋陪,很有可能是線程池里的線程未及時(shí)關(guān)閉導(dǎo)致的贪磺,我們修改代碼來驗(yàn)證

public class MainClass {
    //全局線程池
    static ExecutorService service = Executors.newCachedThreadPool();
    
    public static void main(String[] args) {
        try {
            //定義 線程池硫兰、隊(duì)列、門閂
            
            BlockingQueue<JSONObject> queue = new LinkedBlockingQueue<JSONObject>(100);
            CountDownLatch latch = new CountDownLatch(10);
            //1個(gè)生產(chǎn)者
            Producer producer = new Producer(queue);
            service.execute(producer);
            //10個(gè)消費(fèi)者寒锚,每個(gè)消費(fèi)者加門閂劫映,消費(fèi)完成減一
            for (int i = 0; i < 10; i++) {
                service.submit(new Consumer(queue,latch));
            }

            service.shutdown();
            //主線程等待門閂,都完成后開始第二次循環(huán)
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //輸出線程池狀態(tài)
            System.out.println(service.toString());
            
        }catch (Exception e){
        }
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("循環(huán)一次結(jié)束刹前,第二次開始調(diào)用");
        main(new String[]{});
    }
}

定義全局的線程池變量泳赋,每次輸出線程池狀態(tài)【長度,活動(dòng)線程數(shù)喇喉,完成線程數(shù)】

java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 11]
循環(huán)一次結(jié)束祖今,第二次開始調(diào)用
java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 22]
循環(huán)一次結(jié)束,第二次開始調(diào)用
java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 33]
循環(huán)一次結(jié)束拣技,第二次開始調(diào)用
java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 44]
循環(huán)一次結(jié)束千诬,第二次開始調(diào)用
java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 55]
循環(huán)一次結(jié)束,第二次開始調(diào)用
java.util.concurrent.ThreadPoolExecutor@720653c2[Running, pool size = 11, active threads = 0, queued tasks = 0, completed tasks = 66]
循環(huán)一次結(jié)束膏斤,第二次開始調(diào)用

通過輸出可以看到:

存活線程數(shù)一直是0徐绑,當(dāng)前線程池長度為pool size=11,也就是剛執(zhí)行完的來不及釋放的1個(gè)生產(chǎn)者10個(gè)消費(fèi)者線程莫辨,已完成線程數(shù)completed tasks=11,22,33,44,55,66... 依次增長傲茄。

排除了線程池帶來的內(nèi)存溢出。

main方法無限回調(diào)導(dǎo)致的內(nèi)存問題

為了驗(yàn)證這個(gè)猜想沮榜,設(shè)計(jì)代碼如下

public class MainClass {
    public static void main(String[] args) {
        try {
            //定義 線程池盘榨、隊(duì)列、門閂
            ExecutorService service = Executors.newCachedThreadPool();
            BlockingQueue<JSONObject> queue = new LinkedBlockingQueue<JSONObject>(100);
            CountDownLatch latch = new CountDownLatch(10);
            //new 10個(gè)生產(chǎn)者
            for(int i=0;i<10;i++){
                Producer producer = new Producer(queue);
            }
        }catch (Exception e){
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("循環(huán)一次結(jié)束蟆融,第二次開始調(diào)用");
        main(new String[]{});
    }
}

無限的new對(duì)象较曼,無限的遞歸

通過jvisualvm進(jìn)行監(jiān)控,如下圖


QQ圖片20180918152927.png

可以看到內(nèi)存會(huì)周期性的進(jìn)行回收并保持良好狀態(tài)振愿,這個(gè)猜想也不正確捷犹。

client沒close()導(dǎo)致

最終通過代碼一塊塊的邏輯排除法得出結(jié)論:

是生產(chǎn)者和消費(fèi)者中的連接Elasticsearch的Client使用完畢后,雖然線程關(guān)閉了冕末,但是client沒有關(guān)閉導(dǎo)致的

通過jvisualvm也可以發(fā)現(xiàn)一些線索萍歉,我們使用jvisualvm打開堆文件java_pid42132.hprof

image.png

雙擊打開java.lang.Object[]可以查看它的組成
image.png

一級(jí)一級(jí)的跟下去會(huì)發(fā)現(xiàn)有elasticsearch——client的影子

最后

解決方法很簡單:線程結(jié)束時(shí),關(guān)閉該線程使用的client客戶端

elasticServer.client.close();
System.out.println("consumer end!");
latch.countDown();

我們要注意的就是在數(shù)據(jù)庫連接的處理上要額外注意档桃,一般情況下不會(huì)出問題枪孩,在頻繁的連接釋放和遞歸時(shí),很有可能引起內(nèi)存泄漏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妖泄,更是在濱河造成了極大的恐慌芝薇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件录别,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)低零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拯杠,“玉大人掏婶,你說我怎么就攤上這事√杜悖” “怎么了雄妥?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長依溯。 經(jīng)常有香客問我老厌,道長,這世上最難降的妖魔是什么誓沸? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任梅桩,我火速辦了婚禮,結(jié)果婚禮上拜隧,老公的妹妹穿的比我還像新娘宿百。我一直安慰自己,他們只是感情好洪添,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布垦页。 她就那樣靜靜地躺著,像睡著了一般干奢。 火紅的嫁衣襯著肌膚如雪痊焊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天忿峻,我揣著相機(jī)與錄音薄啥,去河邊找鬼。 笑死逛尚,一個(gè)胖子當(dāng)著我的面吹牛垄惧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绰寞,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼到逊,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼铣口!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起觉壶,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤脑题,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铜靶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叔遂,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年旷坦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掏熬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佑稠。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秒梅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舌胶,到底是詐尸還是另有隱情捆蜀,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布幔嫂,位于F島的核電站辆它,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏履恩。R本人自食惡果不足惜锰茉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望切心。 院中可真熱鬧飒筑,春花似錦、人聲如沸绽昏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽全谤。三九已至肤晓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間认然,已是汗流浹背补憾。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卷员,地道東北人盈匾。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像子刮,于是被迫代替她去往敵國和親威酒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窑睁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn)葵孤,也是為了防止忘記担钮,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦尤仍!如果你也喜歡箫津,那...
    波波波先森閱讀 11,268評(píng)論 4 56
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)宰啦,斷路器苏遥,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 廣袤無垠的撒哈拉有多少粒沙在風(fēng)中掙扎翻滾,平凡的世界里就有多少只靈魂在塵世間愛恨情仇赡模。佛說:阿彌佗佛田炭,一切皆是因緣~
    清輝V閱讀 323評(píng)論 2 1
  • 天亮了,太陽升起漓柑,門外傳來遠(yuǎn)遠(yuǎn)的犬吠聲教硫。 睜開眼,伸個(gè)懶腰辆布,披上外套瞬矩,飲下一杯冽的山泉水,和著一塊小餅锋玲。晨...
    聽歌的古董閱讀 255評(píng)論 0 0
  • 0918(day2) 書名:《贊賞的5種語言》 正文字?jǐn)?shù):720字 1.《學(xué)會(huì)傾聽景用,讓溝通更順暢》 里克.里德是一...
    蘋果Apple來了閱讀 267評(píng)論 0 0