Java的內(nèi)存機制詳解
Java把內(nèi)存分為兩種:一種是棧內(nèi)存,另一種是堆內(nèi)存。在函數(shù)中定義的一些基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配,當(dāng)在一段代碼塊定義一個變量時,Java 就在棧中為這個變量分配內(nèi)存空間,當(dāng)超過變量的作用域后(比如,在函數(shù)A中調(diào)用函數(shù)B,在函數(shù)B中定義變量a,變量a的作用域只是函數(shù)B,在函數(shù)B運行以后,變量a會自動被銷毀。分配給它的內(nèi)存會被回收),Java會自動釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立即另做他用。
堆內(nèi)存用來存放由new創(chuàng)建的內(nèi)存數(shù)組,在堆中分配的內(nèi)存,由Java虛擬機的自動垃圾回收器來管理。在堆中產(chǎn)生一個數(shù)組或?qū)ο笾?,還可以在棧中定義一個特殊的變量,讓棧中的這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個變量就變成了數(shù)組或?qū)ο蟮囊米兞?,以后就可以在程序中使用棧中的變量來訪問堆中的數(shù)組或者對象,引用變量就相當(dāng)于為數(shù)組或者對象起的一個名字。引用變量是普通的變量,定義時在棧中分配,引用變量在程序運行到其他作用域之外后邊釋放。而數(shù)組和對象本省在堆中分配,即使程序運行到使用new產(chǎn)生的數(shù)組或者對象的語句所在的代碼塊之外,數(shù)組和對象本省占據(jù)的內(nèi)存不會被釋放。數(shù)組和對象在沒有引用變量指向它的時候,才變?yōu)槔荒茉俦皇褂?,在隨后的一個不確定時間被垃圾回收器收走(釋放掉)。這也是Java比較占內(nèi)存的原因,實際上,棧中的變量指向堆內(nèi)存中的變量,這就是Java中的指針。
代碼實例Demo1:單個對象創(chuàng)建
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Demo1 {
public static void main(String[] args) {
Person per = new Person() ;
}
}
在上述程序中實例化了一個對象per,在實例化的過程中需要再內(nèi)存中開辟空間,這其中就包括棧內(nèi)存和堆內(nèi)存,具體的內(nèi)存分配如下圖所示:

圖1-1 對象的實例化過程
我們可以從上圖中發(fā)現(xiàn),對象名稱per被保存在了棧內(nèi)存中(更加準確的說法是,在棧內(nèi)存中保存的是堆內(nèi)存空間的訪問地址),而對象的具體內(nèi)容,比如屬性name和age,被保存在堆內(nèi)存中。因為per對象只是被實例化,還沒有被具體賦值,所以都是默認值。字符串的默認值為null,int的類型的默認值為0。前面已經(jīng)提到,堆內(nèi)存空間必須使用new關(guān)鍵字才能開辟。
代碼實例Demo2:多個對象創(chuàng)建
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Demo2 {
public static void main(String[] args) {
Person per1 = new Person() ;
Person per2 = new Person() ;
per1.name="張三" ;
per1.age=30 ;
per2.age=33 ;
per1.tell();
per2.tell();
}
}

圖1-2 實例化兩個對象
關(guān)鍵概念:類跟數(shù)組一樣,都是屬于引用類型,引用類型就是指同一個堆內(nèi)存可以被多個棧內(nèi)存指向,下面來看一下引用傳遞的簡單實例。
代碼實例Demo3:對象引用傳遞1
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Demo3 {
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();
}
}
程序運行結(jié)果為:

從程序的運行結(jié)果可以發(fā)現(xiàn),兩個對象輸出的內(nèi)容一樣,實際上所謂的引用傳遞,就是將一個堆內(nèi)存空間的使用權(quán)交給多個棧內(nèi)存空間,每個棧內(nèi)存空間都可以修改堆內(nèi)存空間的內(nèi)容,此程序的內(nèi)存分配圖如下所示:

圖1-3 對象引用的傳遞內(nèi)存分配

圖1-3 對象引用的傳遞內(nèi)存分配(續(xù))
注意:上述實例中對象per2沒有堆內(nèi)存空間,這是因為對象per2只進行聲明操作,也沒有進行實例化操作。只是使用new關(guān)鍵字,實例化以后才會有堆內(nèi)存空間
代碼實例Demo4:對象引用傳遞2
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:"+name+",年齡:"+age);
}
}
public class Demo4 {
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();
}
}
上述運行程序結(jié)果為:

從程序的輸出結(jié)果可以發(fā)現(xiàn)跟Demo3差不多。不過內(nèi)存分配發(fā)生了一些變化,具體如下所示:

圖1-4 (垃圾對象)的產(chǎn)生
注意點:
1.Java本身提供垃圾收集機制(Garbage Collection,GC),會不定期釋放不用的內(nèi)存空間,只要對象不用了,就會等待GC釋放空間,如上面堆內(nèi)存中的name="李四";age=33。
2.一個棧內(nèi)存只能指向一個堆內(nèi)存空間,如果要想指向其他堆內(nèi)存空間,則必須先斷開已有的指向,才能分配新的指向。
Java中常見的內(nèi)存區(qū)域
在Java中主要存在4塊內(nèi)存空間,這些內(nèi)存的名稱及作用如下:
1.棧內(nèi)存空間:保存所有對象的名稱。
2.堆內(nèi)存空間:保存每個對象的具體屬性內(nèi)容。
3.全局數(shù)據(jù)區(qū):保存static類型的屬性值。
4.全局代碼區(qū):保存所有的方法定義。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)橋接方法isBridge()和合成方法isSynthetic()
本文主要介紹了Java實現(xiàn)橋接方法isBridge()和合成方法isSynthetic(),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
PowerJob?AbstractSqlProcessor方法工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob?AbstractSqlProcessor方法工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
IDEA實現(xiàn)序列化時如何自動生成serialVersionUID的步驟
這篇文章主要介紹了IDEA實現(xiàn)序列化時如何自動生成serialVersionUID的步驟,首先安裝GenerateSerialVersionUID插件,當(dāng)出現(xiàn)添加serialVersionUID選項,選中則會自動生成serialVersionUID,感興趣的朋友一起學(xué)習(xí)下吧2024-02-02

