Java零基礎學習之路(九)Java虛擬機器記憶體管理

qwer1030274531發表於2020-12-09

Java虛擬機器記憶體管理

為了更好的理解上面的程式,先來看看java虛擬機器是如何管理它的記憶體的,請看下圖:

 

圖9-2:java虛擬機器記憶體管理

 

● 程式計數器:

概念:可以看做當前執行緒所執行的位元組碼的行號指示器。

特點:執行緒私有的記憶體

 

● java虛擬機器棧(重點):

概念:描述的是java方法執行的記憶體模型。(每個方法在執行的時候會建立一個 棧幀,用於儲存 區域性變數表,運算元棧,動態連結,方法出口等資訊。每個方法從呼叫直至完成的過程,就對應一個棧幀從入棧到出棧的過程。)

特點:執行緒私有,生命週期和執行緒相同。這個區域會出現兩種異常:StackOverflowError異常:若執行緒請求的深度大於虛擬機器所允許的深度。OutOfMemoryError異常:若虛擬機器可以動態擴充套件,如果擴充套件是無法申請到足夠的記憶體。

 

● 本地方法棧:

概念:它與虛擬機器棧所發揮的作用是相似的,區別是java虛擬機器棧為執行java方法服務,而本地方法棧是為本地方法服務。

特點:執行緒私有,也會丟擲兩類異常:StackOverflowError和OutOfMemoryError。

 

● java堆(重點):

概念:是被所有執行緒共享的一塊區域,在虛擬機器啟動時建立。

特點:執行緒共享,存放的是 物件例項(所有的物件例項和陣列),GC管

理的主要區域。可以處於物理上不連續的記憶體空間。

 

● 方法區(重點):

概念:儲存已被虛擬機器載入的 類資訊、常量、靜態變數,即時編譯器編譯後的程式碼等資料。

特點:執行緒共享的區域,丟擲異常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中怎麼訪問堆記憶體當中的物件?這些你都掌握了嗎。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2740691/,如需轉載,請註明出處,否則將追究法律責任。

相關文章