java虛擬機器記憶體管理
程式計數器:
- 程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。
- 程式計數器處於執行緒獨佔區
- 如果執行緒執行的是 Java 方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址,如果正在執行的 native 方法,這個計數器的值為 undefined
- 此區域是唯一一個 Java 虛擬機器規範中沒有規定任何 OutOfMenmoryError (記憶體溢位)情況的區域。
Java 虛擬機器棧
- 虛擬機器棧描述的是 Java 方法執行的動態記憶體模型
- 棧幀:每個方法執行,都會建立一個棧幀,伴隨這方法從建立到執行完成。用於儲存區域性變量表,運算元棧,動態連結,方法出口等。
- 區域性變量表:( 1 ) . 存放編譯期可知的公眾基本資料型別,引用型別, returnAddress 型別。 ( 2 ) . 區域性變量表的記憶體空間在編譯期完成分配,當進入一個方法時,這個方法需要在幀分配多少記憶體是固定的,在方法執行期間是不會改變區域性變量表的大小
- 大小:當棧幀的記憶體大於 Java 虛擬機器棧的記憶體時,會報 StackOverflowError 或 OutOfMemoryError 記憶體溢位問題
【示例 1-1 】:
public static void test(){ test(); } public static void main(String[] args) { AssetPreservationServer.test(); }
Java 虛擬機器棧記憶體溢位:
Java 方法執行動態記憶體模型圖:
本地方法棧: 與 Java 虛擬機器棧的執行都是一致的,唯一的區別是: Java 虛擬機器棧為虛擬機器執行 Java 方法服務,而本地方法棧為虛擬機器執行 native 方法服務。
Java 堆: 是虛擬機器中管理記憶體最大的一塊區域。也是垃圾收集器主要的管理區域,主要存放物件例項等等。
方法區:
儲存虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。
類資訊包含:類的版本,欄位,介面,方法等等。
執行時常量池:
常量池是屬於方法區
程式碼【例項 1-2 】
public static void main(String[] args) { String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false System.out.println(s1 == s3.intern());//true }
執行是常量池執行模型【示例 1-3 】
解讀:
1.Java 中建立兩個變數 s1,s2
String s1 = "abc";
String s2 = "abc";
2.程式執行到 s1,s2 時, Java 虛擬機器中棧記憶體開闢一塊區域性變數,而這塊區域性變數中存放了 s1 和 s2 兩個變數
3.堆記憶體中就建立 “ abc ” , “ abc ”兩個例項 ,s1 指向一個 abc 例項, s2 指向另外一個“ abc ”例項
4.我們建立的每一個字串的都會放到常量池裡,所以方法區中就建立了一塊常量池,在常量池中,我們可以想象有一張 StringTable 表,而它資料型別為一個 HashSet 集合,用來存放我們所例項化的物件, s1 建立一個“ abc ”,就會放到 HashSet 中,建立一個就存放一個,而 HashSet 的特性是無序不可重複的,所以 s1 和 s2 建立的“ abc ”最後只存放了一個“ abc ”,所以 s1 和 s2 的對應地址顯然是相同的( s1==s2 的值是相等的)
5.再建立一個例項 s3
String s3 = new String("abc");
6.第 5 步建立的例項是我們手動直接建立,我們通過 new 創建出來的物件是直接放在堆記憶體裡,所以就不用去考慮常量池的問題了。就直接在堆記憶體中開闢一塊空間,將值直接賦給了 s3 ,所以 s3==s1 或 s2 時,值是 false 的
7.當我們在 s3 加 intern() 方法時, s1==s3.intern() 的值為 true ,因為 intern() 這個方法會把我們建立的“ abc ”區域,搬到執行時常量池裡面去,產生一個執行時常量,所以 s1==s3.intern() 的值是 true 。