標籤:
Java 把記憶體劃分成兩種:一種是棧記憶體,另一種是堆記憶體。在函數中定義的一些基本類型的變數和對象的引用變數都是在函數的棧記憶體中分配,當在一段代碼塊定義一個變數時,Java 就在棧中為這個變數分配記憶體空間,當超過變數的範圍後(比如,在函數A中調用函數B,在函數B中定義變數a,變數a的範圍只是函數B,在函數B運行完以後,變數a會自動被銷毀。分配給它的記憶體會被回收),Java 會自動釋放掉為該變數分配的記憶體空間,該記憶體空間可以立即被另作它用。
堆記憶體用來存放由 new 建立的對象和數組,在堆中分配的記憶體,由 JAVA 虛擬機器的自動記憶體回收行程來管理。在堆中產生了一個數組或者對象之後,還可以在棧中定義一個特殊的變數,讓棧中的這個變數的取值等於數組或對象在堆記憶體中的首地址,棧中的這個變數就成了數組或對象的引用變數,以後就可以在程式中使用棧中的引用變數來訪問堆中的數組或者對象,引用變數就相當於是為數組或者對象起的一個名稱。引用變數是普通的變數,定義時在棧中分配,引用變數在程式運行到其範圍之外後被釋放。而數組和對象本身在堆中分配,即使程式運行到使用 new 產生數組或者對象的語句所在的代碼塊之外,數組和對象本身佔據的記憶體不會被釋放,數組和對象在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍然佔據記憶體空間不放,在隨後的一個不確定的時間被記憶體回收行程收走(釋放掉)。這也是 Java 比較占記憶體的原因,實際上,棧中的變數指向堆記憶體中的變數,這就是 Java 中的指標!
代碼執行個體Test01:單個對象建立View Code
class Person{
String name;
int age;
public void tell(){
System.out.println("姓名:"+name+"年齡"+age);
}
}
public class Test01 {
public static void main(String[] args) {
Person per=new Person();
}
}
在上述程式中執行個體化了一個對象per,在執行個體化對象的過程中需要在記憶體中開闢空間,這其中就包括棧記憶體和對記憶體。具體的記憶體配置如所示:
我們可以從中發現,對象名稱per被儲存在了棧記憶體中(更加準確的說法是,在棧記憶體中儲存的是堆記憶體空間的訪問地址),而對象的具體內容,比如屬性name和age,被儲存在了堆記憶體中。因為per對象只是被執行個體化,還沒有具體被賦值,所以都是預設值。字串的預設值為null,int類型的預設值為0。前面也已經提到,堆記憶體空間必須使用new關鍵字才能開闢。
代碼執行個體Test02:多個對象建立View Code
class Person{
String name;
int age;
public void tell(){
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Test02 {
public static void main(String[] args) {
Person per1=new Person();
Person per2=new Person();
per1.name="張三";
per1.age=30;
per2.name="李四";
per2.age=33;
per1.tell();
per2.tell();
}
}
關鍵概念:類跟數組一樣,都是屬於參考型別,參考型別就是指一堆對記憶體可以同時被多個棧記憶體指向。下面來看一下引用傳遞的簡單一實例。
代碼執行個體Test03:對象引用傳遞1View Code
class Person{
String name;
int age;
public void tell(){
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Test03{
public static void main(String[] args) {
Person per1=new Person();
Person per2=per1;
per1.name="張三";
per1.age=30;
per2.age=33;
per1.tell();
per2.tell();
}
}
程式運行結果為:
姓名:張三,年齡:33
姓名:張三,年齡:33
從程式的運行結果可以發現,兩個對象輸出的內容一樣,實際上所謂的引用傳遞,就是將一個堆記憶體空間的使用權交個多個棧記憶體空間,每個棧記憶體空間都可以修改堆記憶體空間的內容,此程式的記憶體配置圖如下所示:
注意:上述執行個體中對象per2沒有堆記憶體空間,這是因為對象per2隻進行了聲明操作,也沒有進行執行個體化操作。只有使用new關鍵字執行個體化以後才會有對記憶體空間。
代碼執行個體Test04:對象引用傳遞2View Code
class Person{
String name;
int age;
public void tell(){
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Test04 {
public static void main(String[] args) {
Person per1=new Person();
Person per2=new Person();
per1.name="張三";
per1.age=30;
per2.name="李四";
per2.age=33;
per2=per1;
per1.tell();
per2.tell();
}
}
上述程式運行結果為:
姓名:張三,年齡:30
姓名:張三,年齡:30
從程式的輸出結果可以發現可Test03一樣。不過記憶體配置發生了一些變化,具體如下所示:
注意點:
- Java本身提供垃圾收集機制(Garbage Collection,GC),會不定期施放不用的記憶體空間,只要對象不用了,就會等待GC釋放空間,如上面堆記憶體中的name="李四";age=33。
- 一個棧記憶體只能指向一個對記憶體空間,如果要想再指向其他的堆記憶體空間,則必須先斷開已有的指向才能分配新的指向。
java中常用的記憶體地區
在java中主要存在4塊記憶體空間,這些記憶體的名稱及作用如下:
- 棧記憶體空間:儲存所有的對象名稱(更準確地說是儲存了引用的堆記憶體空間的地址)
- 堆記憶體空間:儲存每個對象的具體屬性內容。
- 全域資料區:儲存static類型的屬性。
- 全域代碼區:儲存所有的方法定義。
Java的記憶體機制