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