場景描述
生產(chǎn)環(huán)境應(yīng)用服務(wù)在運行過程中,內(nèi)存使用量不斷升高,并且沒有下降的趨勢。由服務(wù)剛啟動時10%左右的使用率,之后便緩慢升高。服務(wù)運行大約一星期后內(nèi)存使用率能夠達(dá)到75%。
image
解決步驟
- jdk1.8內(nèi)存模型分析。
JVM內(nèi)存總共分為:
- 虛擬機棧、本地方法棧、pc寄存器(程序計數(shù)器)<線程獨有的內(nèi)存空間>
- 方法區(qū)、堆<線程公共內(nèi)存空間>
五個部分。- 虛擬棧:每個線程獨有的棧。棧中存放有“棧幀”,棧幀中存放有方法的局部變量信息(基本數(shù)據(jù)類型、對象引用),操作數(shù)棧,方法出口等信息。
- 本地方法棧:這部分主要是與java調(diào)用native方法有關(guān)。
- pc寄存器:也叫程序計數(shù)器,記錄線程執(zhí)行指令的地址
- 方法區(qū):存放類的元數(shù)據(jù)信息、常量池、方法數(shù)據(jù)、方法代碼等。(jvm線程共享區(qū)域)
- 堆:線程共享部分,所有對象和數(shù)組都會分配到該區(qū)域,GC主要發(fā)生的區(qū)域
- 結(jié)合JVM內(nèi)存模型與內(nèi)存增長分析,內(nèi)存增長速度增加是在每天用戶量較大時發(fā)生的。因此,判斷出有可能是程序中忘記關(guān)閉資源導(dǎo)致的內(nèi)存泄漏。
- 排查代碼后發(fā)現(xiàn),代碼業(yè)務(wù)代碼有InputStream沒有關(guān)閉。修改之后在生產(chǎn)部署其中一臺服務(wù)觀察內(nèi)存增長情況。
修復(fù)結(jié)果
在關(guān)閉InputStream之后依然沒有效果,內(nèi)存依舊緩慢上升。所以資源沒有關(guān)閉不是癥結(jié)所在。
再次找原因
在服務(wù)器執(zhí)行jstat -gc pid 5000 查看gc情況。
image
從gc次數(shù)與所用時間看,沒有頻繁Fgc的情況,所以判斷應(yīng)該不是堆內(nèi)內(nèi)存的問題。如果是old區(qū)域內(nèi)存快要用完應(yīng)該會發(fā)生頻繁fullgc的情況,或者直接內(nèi)存溢出。
因此,初步判斷因為堆外內(nèi)存使用量過大,并且沒有回收。
先導(dǎo)出堆內(nèi)存快照分析下吧。
jmap -dump:file=javaDump.hprof,format=b pid導(dǎo)出hprof之后使用jdk自帶的工具查看數(shù)量較多的實例進行分析。
經(jīng)過分析,看到fontTrueType這個類的實例比較多,30多w。初步判斷是后臺生成圖片時,創(chuàng)建的字體實例沒有回收造成內(nèi)存泄漏。(字體是讀取的自己下載的ttf文件)
再查閱了一些資料之后,找到了問題所在。https://blog.csdn.net/weixin_34200628/article/details/93182316
原因概述
每次new Font()之后,調(diào)用g.drawString()方法都會在Non-Heap區(qū)域分配一塊內(nèi)存且不回收。由于每次生成圖片時都會new Font()。都會在堆外分配一塊內(nèi)存并且不回收。所以應(yīng)用內(nèi)存無限增長。
解決方案
由于用到的字體為固定的,所以選擇將使用到的字體實例緩存在本地。這樣不用每次使用時都new一個實例,而在堆外分配內(nèi)存。本次使用的是Google 的guava cache將字體實例進行緩存。