Java內(nèi)存溢出和內(nèi)存泄露
雖然jvm可以通過GC自動回收無用的內(nèi)存,但是代碼不好的話仍然存在內(nèi)存溢出的風(fēng)險。
一、為什么要了解內(nèi)存泄露和內(nèi)存溢出?
1、內(nèi)存泄露一般是代碼設(shè)計存在缺陷導(dǎo)致的,通過了解內(nèi)存泄露的場景,可以避免不必要的內(nèi)存溢出和提高自己的代碼編寫水平;
2、通過了解內(nèi)存溢出的幾種常見情況,可以在出現(xiàn)內(nèi)存溢出的時候快速的定位問題的位置,縮短解決故障的時間。
二、基本概念
理解這兩個概念非常重要。
內(nèi)存泄露:指程序中動態(tài)分配內(nèi)存給一些臨時對象,但是對象不會被GC所回收,它始終占用內(nèi)存。即被分配的對象可達但已無用。
內(nèi)存溢出:指程序運行過程中無法申請到足夠的內(nèi)存而導(dǎo)致的一種錯誤。內(nèi)存溢出通常發(fā)生于OLD段或Perm段垃圾回收后,仍然無內(nèi)存空間容納新的Java對象的情況。
從定義上可以看出內(nèi)存泄露是內(nèi)存溢出的一種誘因,不是唯一因素。
三、內(nèi)存泄露的幾種場景:
1、長生命周期的對象持有短生命周期對象的引用
這是內(nèi)存泄露最常見的場景,也是代碼設(shè)計中經(jīng)常出現(xiàn)的問題。
例如:在全局靜態(tài)map中緩存局部變量,且沒有清空操作,隨著時間的推移,這個map會越來越大,造成內(nèi)存泄露。
2、修改hashset中對象的參數(shù)值,且參數(shù)是計算哈希值的字段
當一個對象被存儲進HashSet集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段,否則對象修改后的哈希值與最初存儲進HashSet集合中時的哈希值就不同了,在這種情況下,即使在contains方法使用該對象的當前引用作為參數(shù)去HashSet集合中檢索對象,也將返回找不到對象的結(jié)果,這也會導(dǎo)致無法從HashSet集合中刪除當前對象,造成內(nèi)存泄露。
3、機器的連接數(shù)和關(guān)閉時間設(shè)置
長時間開啟非常耗費資源的連接,也會造成內(nèi)存泄露。
四、內(nèi)存溢出的幾種情況:
1、堆內(nèi)存溢出(outOfMemoryError:java heap space)
在jvm規(guī)范中,堆中的內(nèi)存是用來生成對象實例和數(shù)組的。
如果細分,堆內(nèi)存還可以分為年輕代和年老代,年輕代包括一個eden區(qū)和兩個survivor區(qū)。
當生成新對象時,內(nèi)存的申請過程如下:
a、jvm先嘗試在eden區(qū)分配新建對象所需的內(nèi)存;
b、如果內(nèi)存大小足夠,申請結(jié)束,否則下一步;
c、jvm啟動youngGC,試圖將eden區(qū)中不活躍的對象釋放掉,釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區(qū);
d、Survivor區(qū)被用來作為Eden及old的中間交換區(qū)域,當OLD區(qū)空間足夠時,Survivor區(qū)的對象會被移到Old區(qū),否則會被保留在Survivor區(qū);
e、 當OLD區(qū)空間不夠時,JVM會在OLD區(qū)進行full GC;
f、full GC后,若Survivor及OLD區(qū)仍然無法存放從Eden復(fù)制過來的部分對象,導(dǎo)致JVM無法在Eden區(qū)為新對象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)”out of memory錯誤”:
outOfMemoryError:java heap space
代碼舉例:
/**
* 堆內(nèi)存溢出
*
* jvm參數(shù):-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m
*
*/
public class MemoryLeak {
private String[] s = new String[1000];
public static void main(String[] args) throws InterruptedException {
Map<String,Object> m =new HashMap<String,Object>();
int i =0;
int j=10000;
while(true){
for(;i<j;i++){
MemoryLeak memoryLeak = new MemoryLeak();
m.put(String.valueOf(i), memoryLeak);
}
}
}
}
2、方法區(qū)內(nèi)存溢出(outOfMemoryError:permgem space)
在jvm規(guī)范中,方法區(qū)主要存放的是類信息、常量、靜態(tài)變量等。
所以如果程序加載的類過多,或者使用反射、gclib等這種動態(tài)代理生成類的技術(shù),就可能導(dǎo)致該區(qū)發(fā)生內(nèi)存溢出,一般該區(qū)發(fā)生內(nèi)存溢出時的錯誤信息為:
outOfMemoryError:permgem space
代碼舉例:
1.jvm參數(shù):-XX:PermSize=2m -XX:MaxPermSize=2m
2.將方法區(qū)的大小設(shè)置很低即可,在啟動加載類庫時就會出現(xiàn)內(nèi)存不足的情況
3、線程棧溢出(java.lang.StackOverflowError)
線程棧時線程獨有的一塊內(nèi)存結(jié)構(gòu),所以線程棧發(fā)生問題必定是某個線程運行時產(chǎn)生的錯誤。
一般線程棧溢出是由于遞歸太深或方法調(diào)用層級過多導(dǎo)致的。
發(fā)生棧溢出的錯誤信息為:
java.lang.StackOverflowError
代碼舉例:
/**
* 線程操作棧溢出
*
* 參數(shù):-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m -Xss64k
*
*/
public class StackOverflowTest {
public static void main(String[] args) {
int i =0;
digui(i);
}
private static void digui(int i){
System.out.println(i++);
String[] s = new String[50];
digui(i);
}
}
五、為了避免內(nèi)存泄露,在編寫代碼的過程中可以參考下面的建議:
1、盡早釋放無用對象的引用
2、使用字符串處理,避免使用String,應(yīng)大量使用StringBuffer,每一個String對象都得獨立占用內(nèi)存一塊區(qū)域
3、盡量少用靜態(tài)變量,因為靜態(tài)變量存放在永久代(方法區(qū)),永久代基本不參與垃圾回收
4、避免在循環(huán)中創(chuàng)建對象
5、開啟大型文件或從數(shù)據(jù)庫一次拿了太多的數(shù)據(jù)很容易造成內(nèi)存溢出,所以在這些地方要大概計算一下數(shù)據(jù)量的最大值是多少,并且設(shè)定所需最小及最大的內(nèi)存空間值。
總結(jié)
以上所述是小編給大家介紹的Java內(nèi)存溢出和內(nèi)存泄露,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
SpringBoot打jar包遇到的xml文件丟失的解決方案
這篇文章主要介紹了SpringBoot打jar包遇到的xml文件丟失的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Java動態(tài)追蹤技術(shù)探究之從JSP到Arthas
這篇文章主要介紹了Java動態(tài)追蹤技術(shù)探究之從JSP到Arthas,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,,需要的朋友可以參考下2019-06-06
SpringMVC 重新定向redirect請求中攜帶數(shù)據(jù)方式
這篇文章主要介紹了SpringMVC 重新定向redirect請求中攜帶數(shù)據(jù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java實現(xiàn)markdown格式內(nèi)容轉(zhuǎn)換為word
這篇文章主要為大家簡單介紹了如何利用Java實現(xiàn)markdown格式內(nèi)容轉(zhuǎn)換為word文檔,文中的示例代碼簡潔易懂,有需要的小伙伴可以參考一下2025-03-03
Java基礎(chǔ)詳解之集合框架工具Collections
這篇文章主要介紹了Java基礎(chǔ)詳解之集合框架工具Collections,文中有非常詳細的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-04-04

