一、概述
雖然Java有垃圾收集器幫助實現內存自動管理,雖然GC有效的處理了大部分內存,但是並不能完全保證內存的不泄露。
二、內存泄露
內存泄露就是堆內存中不再使用的對象,但是垃圾回收期無法從內存中刪除他們的情況,因此他們會被不必要的一直存在。這種情況會耗盡內存資源並降低系統性能,最終以OOM終止。
垃圾回收器會定期刪除未引用的對象,但它永遠不會收集那些仍在引用的對象。
內存泄露的症狀:
應用程序長時間連續運行時性能嚴重下降;
應用程序中的OutOfMemoryError堆錯誤;
自發且奇怪的應用程序崩潰;
應用程序偶爾會耗盡連接對象。
三、Java中內存泄露類型
1、static字段引起的內存泄露
大量使用static字段會潛在的導致內存泄露,在Java中,靜態字段通常擁有與整個應用程序相匹配的生命周期。
解決辦法:最大限度的減少靜態變量的使用;單例模式時,依賴於延遲加載對象而不是立即加載方式。
2、未關閉的資源導致內存泄露
每當創建連接或者打開流時,JVM都會為這些資源分配內存。如果沒有關閉連接,會導致持續占有內存。在任意情況下,資源留下的開放連接都會消耗內存,如果我們不處理,就會降低性能,甚至OOM。
解決辦法:使用finally塊關閉資源;關閉資源的代碼,不應該有異常;jdk1.7后,可以使用try-with-resource塊。
3、不正確的equals()和hashCode()
在HashMap和HashSet這種集合中,常常用到equal()和hashCode()來比較對象,如果重寫不合理,將會成為潛在的內存泄露問題。
解決辦法:用最佳的方式重寫equals()和hashCode。
4、引用了外部類的內部類
非靜態內部類的初始化,總是需要外部類的實例;默認情況下,每個非靜態內部類都包含對其包含內的隱式引用,如果我們在應用程序中使用這個內部類對象,那么即使在我們的包含類對象超出范圍后,它也不會被垃圾收集。
解決辦法:如果內部類不需要訪問包含的類成員,考慮轉換為靜態類。
5、finalize()方法造成的內存泄露
重寫finalize()方法時,該類的對象不會立即被垃圾收集器收集,如果finalize()方法的代碼有問題,那么會潛在的引發OOM;
解決辦法:避免重寫finalize()。
6、常量字符串造成的內存泄露
如果我們讀取一個很大的String對象,並調用了inter(),那么它將放到字符串池中,位於PermGen中,只要應用程序運行,該字符串就會保留,這就會占用內存,可能造成OOM。
解決辦法:增加PermGen的大小,-XX:MaxPermSize=512m;升級Java版本,JDK1.7后字符串池轉移到了堆中。
7、使用ThreadLocal造成內存泄露
使用ThreadLocal時,每個線程只要處於存貨狀態就可保留對其ThreadLocal變量副本的隱式調用,且將保留其自己的副本。使用不當,就會引起內存泄露。
一旦線程不在存在,ThreadLocals就應該被垃圾收集,而現在線程的創建都是使用線程池,線程池有線程重用的功能,因此線程就不會被垃圾回收器回收。所以使用到ThreadLocals來保留線程池中線程的變量副本時,ThreadLocals沒有顯示的刪除時,就會一直保留在內存中,不會被垃圾回收。
解決辦法:不在使用ThreadLocal時,調用remove()方法,該方法刪除了此變量的當前線程值。不要使用ThreadLocal.set(null),它只是查找與當前線程關聯的Map並將鍵值對設置為當前線程為null。
作者:落地生涯
鏈接:https://www.jianshu.com/p/280b99887659
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。