這兩天看了關於JVM記憶體相關的一些資料,為了方便以後查閱,在此留下一篇博文,順便加深理解。另外,可參考:http://web.cutt.com/article/1740661967
根據《分布式JAVA應用、基礎與實踐》一書中的說明,JVM規範將記憶體空間劃分為方法區、堆、本地方法棧、PC寄存器及JVM方法棧。如所示:
方法區:存放了要載入的類的資訊(名稱、修飾符等)、類中的靜態變數、類中定義為final類型的常量、類中的Field資訊、類中的方法資訊。方法地區是全域共用的,在一定條件下它也會被GC,當方法區要使用的記憶體超過其允許的大小時,會報出OutOfMemory的錯誤資訊。
本地方法棧:使用者支援native方法的執行,儲存了每個native方法調用的狀態,在Sun JDK的實現中本地方法棧和JVM方法棧是同一個。
PC寄存器和JVM方法棧:每個線程均會建立PC寄存器和JVM方法棧,PC寄存器佔用的可能為CPU寄存器或作業系統記憶體,JVM方法棧佔用的為作業系統記憶體,JVM方法棧為線程私人。當方法運行完畢時,其對應的棧幀所佔用的記憶體也會自動釋放。
下面,從java涉及到的類型來描述。
1、java虛擬機器記憶體原型
寄存器:不屬於記憶體,我們在程式中無法控制。
棧:存放基本類型的資料和對象的引用,但對象本身是放在堆中的。
堆:存在對象。
靜態域:存在在對象中用static定義的靜態成員。
常量池:存放常量。
2、常量池
常量池指的是在編譯期間被確定,並被儲存在已編譯的class檔案中的一些資料。在程式執行的時候,常量池會儲存在方法區中。
3、java記憶體中分配的棧
棧的基本單位是幀(或棧幀)。每當一個java線程啟動並執行時候,java虛擬機器會為該線程分配一個java棧。java棧中的所有資料都是私人的,其他線程都不能訪問該線程的棧資料。當在一段代碼塊定義一個變數時,java就在棧中為這個變數分配記憶體空間,當該變數推出該範圍後,java會自動釋放掉為該變數所分配的記憶體空間。很明顯,這部分對應JVM方法棧。
4、java記憶體中分配的堆
java虛擬機器中的堆用來存放由new建立的對象和數組。
注意:引用變數是普通的變數,定義時在棧中分配,引用變數在程式運行到其範圍之外後被釋放。實際上,棧中的變數指向堆記憶體中的變數,這就是java中的指標!
堆是由記憶體回收來負責的,堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配記憶體的,java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在運行時動態分配記憶體,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧資料可以共用。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。
棧有一個很重要的特殊性,就是存在棧中的資料可以共用。假設我們同時定義:
int a=3; int b=3; 編譯器先處理int a = 3;首先它會在棧中建立一個變數為a的引用,然後尋找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3.接著處理int b = 3;在建立完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3.這樣,就出現了a與b同時均指向3的情況。
這時,如果再令a=4;那麼編譯器會重新搜尋棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響 到b的值。
要注意這種資料的共用與兩個對象的引用同時指向一個對象的這種共用是不同的,因為這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間的。而一個對象引用變數修改了這個對象的內部狀態,會影響到另一個對象引用變數。