為了更好的理解上面的程序,先來看看java虛擬機是如何管理它的內(nèi)存的,請看下圖:
圖9-2:java虛擬機內(nèi)存管理
● 程序計數(shù)器:
概念:可以看做當前線程所執(zhí)行的字節(jié)碼的行號指示器。
特點:線程私有的內(nèi)存
● java虛擬機棧(重點):
概念:描述的是java方法執(zhí)行的內(nèi)存模型。(每個方法在執(zhí)行的時候會創(chuàng)建一個棧幀,用于存儲局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口等信息。每個方法從調(diào)用直至完成的過程,就對應一個棧幀從入棧到出棧的過程。)
特點:線程私有,生命周期和線程相同。這個區(qū)域會出現(xiàn)兩種異常:StackOverflowError異常:若線程請求的深度大于虛擬機所允許的深度。OutOfMemoryError異常:若虛擬機可以動態(tài)擴展,如果擴展是無法申請到足夠的內(nèi)存。
● 本地方法棧:
概念:它與虛擬機棧所發(fā)揮的作用是相似的,區(qū)別是java虛擬機棧為執(zhí)行java方法服務,而本地方法棧是為本地方法服務。
特點:線程私有,也會拋出兩類異常:StackOverflowError和OutOfMemoryError。
● java堆(重點):
概念:是被所有線程共享的一塊區(qū)域,在虛擬機啟動時創(chuàng)建。
特點:線程共享,存放的是對象實例(所有的對象實例和數(shù)組),GC管
理的主要區(qū)域??梢蕴幱谖锢砩喜贿B續(xù)的內(nèi)存空間。
● 方法區(qū)(重點):
概念:存儲已被虛擬機加載的類信息、常量、靜態(tài)變量,即時編譯器編譯后的代碼等數(shù)據(jù)。
特點:線程共享的區(qū)域,拋出異常OutOfMemory異常:當方法區(qū)無法滿足內(nèi)存分配需求的時候。
以上所描述內(nèi)容,有看得懂的,也有看不懂的,例如:線程、本地方法等,這個需要大家在學習后面內(nèi)容之后,返回來再看一看,那個時候你就全部明白了。針對于目前來說,大家必須要知道java虛擬機有三塊主要的內(nèi)存空間,分別是“虛擬機棧(后面簡稱棧)”、“方法區(qū)”、“堆區(qū)”,方法區(qū)存儲類的信息,棧中存儲方法執(zhí)行時的棧幀以及局部變量,堆區(qū)中主要存儲new出來的對象,以及對象內(nèi)部的實例變量。其中垃圾回收器主要針對的是堆內(nèi)存,方法區(qū)中最先有數(shù)據(jù),因為程序執(zhí)行之前會先進行類加載。棧內(nèi)存活動最頻繁,因為方法不斷的執(zhí)行并結(jié)束,不斷的進行壓棧彈棧操作。將目前階段需要掌握的內(nèi)存空間使用一張簡單的圖表示出來,這個圖是大家需要掌握的:
圖9-3:java虛擬機內(nèi)存管理簡圖
大概了解了java虛擬機內(nèi)存分配之后,來看看以下代碼在執(zhí)行過程中,內(nèi)存是如何變化的:
public class StudentTest {
public static void main(String[] args) {
int i = 10;
Student s1 = new Student();
}
}
以上代碼在執(zhí)行過程中內(nèi)存的變化如下圖所示:
圖9-4:第一步進行類加載
圖9-5:第二步main方法調(diào)用,給main方法分配棧幀(壓棧)
圖9-6:第三步執(zhí)行int i = 10,局部變量
圖9-7:第四步執(zhí)行new Student(),在堆中創(chuàng)建對象,同時初始化實例變量
圖9-8:第五步將堆區(qū)中學生對象的內(nèi)存地址賦值給局部變量s1
注意:上圖所描述內(nèi)存圖有些地方為了幫助大家更好的理解,有些位置畫的不是很精確,隨著后面內(nèi)容的學習我們再進一步修改,目前上圖已經(jīng)夠大家用了。
上圖中i變量和s1變量都是局部變量,都在棧內(nèi)存當中,只不過i變量是基本數(shù)據(jù)類型int,而s1變量是引用數(shù)據(jù)類型Student。
上圖中堆區(qū)當中的稱為“對象”,該“對象”內(nèi)部no、name、age、sex都是實例變量/屬性,這些變量在new對象的時候初始化,如果沒有手動賦值,系統(tǒng)會賦默認值。
上圖堆區(qū)中“對象”創(chuàng)建完成之后,該對象在堆區(qū)當中的內(nèi)存地址是:0x1111,程序中的“=”將0x1111這個堆內(nèi)存地址賦值給s1變量,也就是說s1變量保存了堆內(nèi)存對象的內(nèi)存地址,我們對于這種變量有一種特殊的稱呼,叫做“引用”。也就是說對于Student s1 = new Student()代碼來說,s1不是對象,是一個引用,對象實際上是在堆區(qū)當中,s1變量持有這個對象的內(nèi)存地址。
java中沒有指針的概念(指針是C語言當中的機制),所以java程序員沒有權利直接操作堆內(nèi)存,只能通過“引用”去訪問堆內(nèi)存中的對象,例如:s1.no、s1.name、s1.sex、s1.age。訪問一個對象的內(nèi)存,其實就是訪問該對象的實例變量,而訪問實例變量通常包括兩種形式,要么就是讀取數(shù)據(jù),要么就是修改數(shù)據(jù),例如:System.out.println(s1.no)這就是讀取數(shù)據(jù),s1.no = 100這就是修改數(shù)據(jù)。請看以下代碼:
public class StudentTest {
public static void main(String[] args) {
int i = 10;
Student s1 = new Student();
s1.no = 100;
s1.name = "zhangsan";
s1.sex = true;
s1.age = 20;
System.out.println("學號 = " + s1.no);
System.out.println("姓名 = " + s1.name);
System.out.println("性別 = " + s1.sex);
System.out.println("年齡 = " + s1.age);
}
}
運行結(jié)果如下所示:
圖9-9:修改實例變量之后的執(zhí)行結(jié)果
執(zhí)行了以上程序之后,堆內(nèi)存對象的實例變量發(fā)生了變化,如下圖所示:

圖9-10:實例變量執(zhí)行賦值運算之后的內(nèi)存圖
如果基于以上的代碼再創(chuàng)建一個對象,內(nèi)存圖會是怎么的呢?先看代碼:
public class StudentTest {
public static void main(String[] args) {
int i = 10;
Student s1 = new Student();
Student s2 = new Student();
}
}
JVM內(nèi)存結(jié)構圖如下所示:
圖9-11:創(chuàng)建多個對象的內(nèi)存結(jié)構圖
通過上圖的學習,可以看出假設new出100個學生對象,會有100個no,100個age...是這樣吧。
通過以上內(nèi)容的學習,需要每位同學掌握:局部變量存儲在哪里?實例變量存儲在哪里?實例變量在什么時候初始化?對象和引用有什么區(qū)別?在java中怎么訪問堆內(nèi)存當中的對象?這些你都掌握了嗎。