很多人在Java的書籍中看到過很多關于堆和棧記憶體的教程以及參考說明, 但是很難解釋什麼是程式的堆記憶體以及棧記憶體
一: Java 堆記憶體空間
Java程式運作時使用java Heap 記憶體為對象以及JRE類配置設定記憶體, 不論我們在何時建立何種類型的對象, 他總是在堆記憶體中建立的
Java 垃圾收集器運作在堆内容空間, 釋放那些沒有任何引用的對象所使用的記憶體。 在堆記憶體空間建立的任何對象都具有全局通路權限, 并且可以從程式的任何位置引用
二: Java 棧記憶體空間
Java 棧記憶體空間用于執行線程, 棧記憶體始終遵循LIFO(Last-in-first-out) 順序, 每當一個方法被執行, 會在棧記憶體中建立一個新的block 用于儲存在函數中定義的基本資料類型變量以及對象的引用變量
當方法結束時, this block 改變它的狀态為未使用并且可用于執行下一個方法
堆記憶體大小與堆記憶體相比非常少。
三: Java程式中的堆和堆棧記憶體
通過一個簡單的程式來了解堆 棧記憶體的使用情況
/**
* Created by huanjulu on 12/10/17.
*/
public class HeapStackTestMemory {
public static void main(String[] args) { //Line 1
int i = ; //Line 2
Object obj = new Object(); //Line 3
HeapStackTestMemory mem = new HeapStackTestMemory(); //Line 4
mem.foo(obj); //Line 5
} //Line 9
private void foo(Object param) { //Line 6
String str = param.toString(); //Line 7
System.out.println(str);
} //Line 8
}
參考上面的java 程式, 下圖顯示了stack Heap 記憶體空間的使用情況
我們來看看執行程式的步驟。
- 一旦我們開始運作程式, 它會把所有的運作時類加載到堆記憶體空間, 在 Line 1 行找到main() 方法, Java Runtime 建立由main() 方法線程使用的棧記憶體空間
- 在第二行 我們建立了原始資料類型的局部變量, 是以它将被存儲在main() 方法的棧記憶體空間
- 在第3行我們建立了一個Object 類型的對象, 是以它被建立在Heap 堆記憶體空間中 并且 Stack 棧記憶體空間包含對它的引用, 當我們在第4行中建立Memory 對象時, 會發生類似的過程
- 現在我們在第5行調用foo() 方法, 此時會在stack 棧建立一個block 供foo() 方法使用
- Java 是通過值傳遞, 在第6行, 會在foo() 棧中建立一個對Object 對象的新的引用
- 在第7行 , 一個string 類型的對象被建立, 此時 會在foo() 棧記憶體中建立它的一個引用 str
- foo() 方法在第8行執行完畢, 此時, 程式會釋放stack 棧記憶體中為foo() 方法配置設定的棧記憶體空間
-
在第9行, main() 方法執行完畢, 為main()方法建立的堆棧記憶體被銷毀, 此時 這個java 程式結束運作, Java Runtime 會釋放所有的記憶體
四: Java Heap Difference with Stack Memory Space
基于上述的說明, 可以很容易的總結出堆棧記憶體的以下差異
1, 堆記憶體屬于java 應用程式所使用, 棧記憶體屬于線程所私有的, 它的生命周期與線程相同
2, 不論何時建立一個對象, 它總是存儲在堆記憶體空間 并且棧記憶體空間包含對它的引用 . 棧記憶體空間隻包含方法原始資料類型局部變量以及堆空間中對象的引用變量
3, 在堆中的對象可以全局通路, 棧記憶體空間屬于線程所私有
4, jvm 棧記憶體結構管理較為簡單, 遵循LIFO 的原則, 堆空間記憶體管理較為複雜 , 細分為:新生代和老年代 etc..
5, 棧記憶體生命周期短暫, 而堆記憶體伴随整個用用程式的生命周期
6, 二者抛出異常的方式, 如果線程請求的棧深度大于虛拟機所允許的深度,将抛出StackOverflowError異常, 堆記憶體抛出OutOfMemoryError異常