一篇文章帶你搞定JAVA內(nèi)存泄漏
1、什么是內(nèi)存泄漏
內(nèi)存泄漏是指無(wú)用對(duì)象(不再使用的對(duì)象)持續(xù)占有內(nèi)存或無(wú)用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏。隨著垃圾回收器活動(dòng)的增加以及內(nèi)存占用的不斷增加,程序性能會(huì)逐漸表現(xiàn)出來(lái)下降,極端情況下,會(huì)引發(fā)OutOfMemoryError導(dǎo)致程序崩潰。
2、內(nèi)存泄漏的原因
JVM 虛擬機(jī)是使用引用計(jì)數(shù)法和可達(dá)性分析來(lái)判斷對(duì)象是否可回收,本質(zhì)是判斷一個(gè)對(duì)象是否還被引用,如果沒(méi)有引用則回收。在開(kāi)發(fā)的過(guò)程中,由于代碼的實(shí)現(xiàn)不同就會(huì)出現(xiàn)很多種內(nèi)存泄漏問(wèn)題,讓gc 系統(tǒng)誤以為此對(duì)象還在引用中,無(wú)法回收,造成內(nèi)存泄漏。
3、內(nèi)存泄漏有哪些情況
3.1 代碼中沒(méi)有及時(shí)釋放,導(dǎo)致內(nèi)存無(wú)法回收。
下面的代碼,因?yàn)槭请p向鏈表,但是斷開(kāi)的不夠徹底,prev節(jié)點(diǎn)依然引用這當(dāng)前正在使用的節(jié)點(diǎn),導(dǎo)致無(wú)法回收
public class ListNode { int val; ListNode next; ListNode prev; ListNode() { } ListNode(int val) { this.val = val; } public ListNode(int val, ListNode next, ListNode prev) { this.val = val; this.next = next; this.prev = prev; } public static void main(String[] args) { ListNode curr = new ListNode(1); ListNode prev = new ListNode(2); ListNode next = new ListNode(3); curr.prev = prev; curr.next = next; curr.prev = null; } }
public static void main(String[] args) { ListNode curr = new ListNode(1); ListNode prev = new ListNode(2); ListNode next = new ListNode(3); curr.prev = prev; curr.next = next; curr.prev = null; } }
3.2 資源未關(guān)閉造成的內(nèi)存泄漏
各種連接,如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接和IO連接等,文件讀寫(xiě)等,可以使用 try-with-resources 讀取完文件,自動(dòng)資源釋放
try (RandomAccessFile raf = new RandomAccessFile(filePath, "r");) { Image image = null; while((image = parseImage(raf)) != null){ imageList.add(image); } return imageList; } catch(Exception e){ log.error("parse file error, path: {},", path, e); return null; }
3.3 全局緩存持有的對(duì)象不使用的時(shí)候沒(méi)有及時(shí)移除,導(dǎo)致一直在內(nèi)存中無(wú)法移除
3.4 靜態(tài)集合類
如HashMap、LinkedList等等。如果這些容器為靜態(tài)的,那么它們的生命周期與程序一致,則容器中的對(duì)象在程序結(jié)束之前將不能被釋放,從而造成內(nèi)存泄漏。生命周期長(zhǎng)的對(duì)象持有短生命周期對(duì)象的引用,盡管短生命周期的對(duì)象不再使用,但是因?yàn)殚L(zhǎng)生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收。
3.5 堆外內(nèi)存無(wú)法回收
堆外內(nèi)存不受gc的管理,可能因?yàn)榈谌降腷ug出現(xiàn)內(nèi)存泄漏
4、內(nèi)存泄漏的解決辦法
1.盡量減少使用靜態(tài)變量,或者使用完及時(shí) 賦值為 null。
2.明確內(nèi)存對(duì)象的有效作用域,盡量縮小對(duì)象的作用域,能用局部變量處理的不用成員變量,因?yàn)榫植孔兞繌棗?huì)自動(dòng)回收;
3.減少長(zhǎng)生命周期的對(duì)象持有短生命周期的引用;
4.使用StringBuilder和StringBuffer進(jìn)行字符串連接,Sting和StringBuilder以及StringBuffer等都可以代表字符串,其中String字符串代表的是不可變的字符串,后兩者表示可變的字符串。如果使用多個(gè)String對(duì)象進(jìn)行字符串連接運(yùn)算,在運(yùn)行時(shí)可能產(chǎn)生大量臨時(shí)字符串,這些字符串會(huì)保存在內(nèi)存中從而導(dǎo)致程序性能下降。
5.對(duì)于不需要使用的對(duì)象手動(dòng)設(shè)置null值,不管GC何時(shí)會(huì)開(kāi)始清理,我們都應(yīng)及時(shí)的將無(wú)用的對(duì)象標(biāo)記為可被清理的對(duì)象;
6.各種連接(數(shù)據(jù)庫(kù)連接,網(wǎng)絡(luò)連接,IO連接)操作,務(wù)必顯示調(diào)用close關(guān)閉。
5、內(nèi)存問(wèn)題排查
沒(méi)有任何一個(gè)程序員想要出現(xiàn)這種問(wèn)題,但是出現(xiàn)了問(wèn)題也要解決,內(nèi)存泄漏的主要表象就是內(nèi)存不足,內(nèi)存告警之后如何判斷是否有內(nèi)存泄漏。
第一步 首先確認(rèn)邏輯問(wèn)題
查看內(nèi)存中對(duì)象的數(shù)量和大小,判斷是否在合理的范圍,如果在合理的范圍內(nèi),增大內(nèi)存配置,調(diào)整內(nèi)存比例就可以了。
命令:
jmap -heap pid
第二步:分析gc是否正常執(zhí)行
命令:
jstat -gcutil <pid> 1000
S0 — Heap上的 Survivor space 0 區(qū)已使用空間的百分比 S1 — Heap上的 Survivor space 1 區(qū)已使用空間的百分比 E — Heap上的 Eden space 區(qū)已使用空間的百分比 O — Heap上的 Old space 區(qū)已使用空間的百分比 P — Perm space 區(qū)已使用空間的百分比 YGC — 從應(yīng)用程序啟動(dòng)到采樣時(shí)發(fā)生 Young GC 的次數(shù) YGCT– 從應(yīng)用程序啟動(dòng)到采樣時(shí) Young GC 所用的時(shí)間(單位秒) FGC — 從應(yīng)用程序啟動(dòng)到采樣時(shí)發(fā)生 Full GC 的次數(shù) FGCT– 從應(yīng)用程序啟動(dòng)到采樣時(shí) Full GC 所用的時(shí)間(單位秒) GCT — 從應(yīng)用程序啟動(dòng)到采樣時(shí)用于垃圾回收的總時(shí)間(單位秒) LGCC - 進(jìn)行GC的原因(低版本jdk可能沒(méi)有這一列)
從這里觀察gc是否異常,也可以根據(jù)這個(gè)進(jìn)行jvm內(nèi)存分配調(diào)優(yōu),來(lái)提高性能降低gc對(duì)性能的損耗
第三步 確認(rèn)下版本新增代碼的改動(dòng),盡快從代碼上找出問(wèn)題。
第四步:開(kāi)啟各種命令行和 導(dǎo)出 dump 各種工具分析
-XX:+HeapDumpOnOutOfMemoryError -XX:OnError -XX:+ShowMessageBoxOnError
推薦使用jprofile 進(jìn)行本地分析,可以不用記住那么多命令。
總結(jié):
現(xiàn)在的服務(wù)器內(nèi)存雖然很大,但是且用且珍惜,不要等到出現(xiàn)問(wèn)題了才知道后果,在開(kāi)發(fā)中規(guī)范自己代碼,用完的對(duì)象及時(shí)釋放,減少垃圾對(duì)象。出現(xiàn)問(wèn)題了也不要慌,仔細(xì)分析代碼,一切都是有原因的。
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
MyBatis之關(guān)于動(dòng)態(tài)SQL解讀
這篇文章主要介紹了MyBatis之關(guān)于動(dòng)態(tài)SQL解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06java阿拉伯?dāng)?shù)字轉(zhuǎn)中文數(shù)字
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)阿拉伯?dāng)?shù)字轉(zhuǎn)換為中文數(shù)字,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04淺談Java中@Autowired和@Inject注解的區(qū)別和使用場(chǎng)景
本文主要介紹了淺談Java中@Autowired和@Inject注解的區(qū)別和使用場(chǎng)景,@Autowired注解在依賴查找方式和注入方式上更加靈活,適用于Spring框架中的依賴注入,而@Inject注解在依賴查找方式上更加嚴(yán)格,適用于Java的依賴注入標(biāo)準(zhǔn),感興趣的可以了解一下2023-11-11Java并發(fā)編程之volatile與JMM多線程內(nèi)存模型
這篇文章主要介紹了Java并發(fā)volatile與JMM多線程內(nèi)存模型,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05Java基礎(chǔ)之extends用法詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了 Java基礎(chǔ)之extends用法詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02springboot?log4j2日志框架整合與使用過(guò)程解析
這篇文章主要介紹了springboot?log4j2日志框架整合與使用,包括引入maven依賴和添加配置文件log4j2-spring.xml的相關(guān)知識(shí),需要的朋友可以參考下2022-05-05Java獲得一個(gè)數(shù)組的指定長(zhǎng)度排列組合算法示例
這篇文章主要介紹了Java獲得一個(gè)數(shù)組的指定長(zhǎng)度排列組合算法,結(jié)合實(shí)例形式分析了java排列組合相關(guān)數(shù)組遍歷、運(yùn)算操作技巧,需要的朋友可以參考下2019-06-06