Java 堆疊記憶體分配
很多人在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 = 1; //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異常