必須先要了解的
1。 c/c++是程式設計師自己管理內存,Java內存是由GC自動回收的。
我雖然不是很熟悉C++,不過這個應該沒有犯常識性錯誤吧。
2。什麼是內存洩漏?
內存外洩是指系統中存在無法回收的內存,有時會造成內存不足或系統崩潰。
在C/C++中分配了記憶體不釋放的情況就是記憶體外洩。
3。 Java存在記憶體外洩我們必須先承認這個,才可以接著討論。雖然Java有記憶體洩露,但基本上不用很在意它,特別是那些對程式碼本身就不講究的就更不要去關心這個了。
Java中的記憶體外洩當然是指:存在無用但是垃圾回收器無法回收的物件。
而且即使有記憶體外洩問題存在,也不一定會表現出來。
4。 Java中參數都是傳值的。
對於基本類型,大家基本上沒有異議,但是對於引用類型我們也不能有異議。
Java記憶體外洩狀況
1.堆記憶體溢位(outOfMemoryError:java heap space)
在jvm規格中,堆中的記憶體是用來產生物件實例和陣列的。
如果細分,堆記憶體還可以分為年輕代和年老代,年輕代包括一個eden區和兩個survivor區。
當產生新物件時,記憶體的申請過程如下:
a、jvm先嘗試在eden區分配新物件所需的記憶體;
b、如果記憶體大小足夠,申請結束,否則下一步;
c、jvm啟動youngGC,試圖將eden區中不活躍的對象釋放掉,釋放後若Eden空間仍不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區;
d、Survivor區被用來作為Eden及old的中間交換區域,當OLD區空間足夠時,Survivor區的物件會被移到Old區,否則會被保留在Survivor區;
e、 當OLD區空間不夠時,JVM會在OLD區進行full GC;
f、full GC後,若Survivor及OLD區仍無法存放從Eden複製過來的部分對象,導致JVM無法在Eden區為新物件建立記憶體區域,則出現」out of memory錯誤」:
outOfMemoryError:java heap space
2.方法區記憶體溢位(outOfMemoryError:permgem space)
在jvm規格中,方法區主要存放的是類別資訊、常數、靜態變數等。
所以如果程式載入的類別過多,或是使用反射、gclib等這種動態代理產生類別的技術,就可能導致該區發生記憶體溢出,一般該區發生記憶體溢出時的錯誤訊息為:
outOfMemoryError:permgem space
3.線程棧溢出(java。lang。StackOverflowError)
執行緒棧時執行緒獨有的一塊記憶體結構,所以執行緒棧發生問題必定是某個執行緒執行時產生的錯誤。
一般執行緒棧溢位是由於遞歸太深或方法呼叫層級過多所導致的。
發生棧溢位的錯誤訊息為:
java。 lang。 StackOverflowError
內存外洩的幾種場景:
1、長生命週期的物件持有短生命週期物件的引用
這是記憶體外洩最常見的場景,也是程式碼設計中常出現的問題。
例如:在全域靜態map中快取局部變量,且沒有清空操作,隨著時間的推移,這個map會越來越大,造成記憶體外洩。
2.修改hashset中物件的參數值,且參數是計算哈希值的字段
當一個物件被儲存進HashSet集合中以後,就不能修改這個物件中的那些參與計算哈希值的字段,否則物件修改後的雜湊值與最初儲存在HashSet集合中時的雜湊值就不同了,在這種情況下,即使在contains方法使用該對象的當前引用作為參數去HashSet集合中檢索對象,也將返回找不到對象的結果,這也會導致無法從HashSet集合中刪除當前對象,造成內存外洩。
3、機器的連線數和關閉時間設定
長時間開啟非常耗費資源的連接,也會造成記憶體外洩。
來看個記憶體外洩的例子:
public class Stack { private Object[] elements=new Object[10]; private int size = 0; public void push(Object e){ ensureCapacity(); elements[size++] = e; } public Object pop(){ if( size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity(){ if(elements。length == size){ Object[] oldElements = elements; elements = new Object[2 * elements。 length+1]; System。 arraycopy(oldElements,0, elements, 0, size); } }}
上面的原理應該很簡單,假如堆疊加了10個元素,然後全部彈出來,雖然堆疊是空的,沒有我們要的東西,但是這是個物件是無法回收的,這個才符合了記憶體外洩的兩個條件:無用,無法回收。
但是就是存在這樣的東西也不一定會導致什麼樣的後果,如果這個堆疊用的比較少,
也就浪費了幾個K內存而已,反正我們的內存都上G了,哪裡會有什麼影響,再說這個東西很快就會被回收的,有什麼關係。下面看兩個例子。
例子1
public class Bad{ public static Stack s=Stack(); static{ s。 push(new Object()); s。 pop(); //這裡有一個物件發生記憶體洩漏s。 push(new Object()); //上面的物件可以被回收了,等於是自癒了}}
因為是static,就一直存在到程式退出,但我們也可以看到它有自癒功能,
是說如果你的Stack最多有100個對象,那麼最多也就只有100個對象無法被回收其實這個應該很容易理解,Stack內部持有100個引用,最壞的情況就是他們都是無用的,因為我們一旦放新的進取,以前的引用自然消失!
例子2
public class NotTooBad{ public void doSomething(){ Stack s=new Stack(); s。 push(new Object()); //other code s。 pop();//這裡同樣導致物件無法回收,記憶體外洩。 }//退出方法,s自動無效,s可以被回收,Stack內部的引用自然沒了,所以//這裡也可以自愈,而且可以說這個方法不存在內存洩漏問題,不過是晚一點//交給GC而已,因為它是封閉的,對外不開放,可以說上面的代碼99。9999%的//情況是不會造成任何影響的,當然你寫這樣的代碼不會有什麼壞的影響,但是//絕對可以說是垃圾代碼!沒有矛盾吧,我在裡面加一個空的for循環也不會有//什麼太大的影響吧,你會這麼做嗎?}
上面兩個例子都不過是小打小鬧,但是C/C++中的記憶體外洩就不是Bad了,而是Worst了。
他們如果一處沒有回收就永遠無法回收,頻繁的調用這個方法內存不就用光了!
因為Java還有自癒功能(我自己起的名字,還沒申請專利),所以Java的內存洩漏問題幾乎可以忽略了,但是知道的人就不要犯了。
為了避免記憶體洩露,在編寫程式碼的過程中可以參考下面的建議:
1、儘早釋放無用物件的引用;
2.使用字串處理,避免使用String,應大量使用StringBuffer,每一個String物件都得獨立佔用記憶體一塊區域;
3.盡量少用靜態變量,因為靜態變數存放在永久代(方法區),永久代基本上不參與垃圾回收;
4、避免在循環中創建物件;
5.開啟大型檔案或從資料庫一次拿了太多的資料很容易造成記憶體溢出,所以在這些地方要大概計算一下資料量的最大值是多少,並且設定所需最小及最大的記憶體空間值。