深入理解java虛擬機系列(一):java內存區域與內存溢出異常

文章主要是閱讀《深入理解java虛擬機:JVM高級特性與最佳實踐》第二章:Java內存區域與內存溢出異常

的一些筆記以及概括。


好了開始。如果有什麼錯誤或者遺漏,歡迎指出。


一、概述

先上一張圖


這張圖主要列出了Java虛擬機管理的內存的幾個區域。

常有人把Java內存區分爲堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java內存區域的劃分實際上遠比這複雜,從上圖就可以看出了。堆棧分法中所指的“棧”實際上只是虛擬機棧,或者說是虛擬機棧中的局部變量表部分。接下來主要講解區域的各個部分.


二、運行時數據區域


1.程序計數器

  • .程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令
  • Java虛擬機的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)只會執行一條線程中的指令。因此,爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域爲“線程私有”的內存。
  •  線程正在執行的是一個Java方法,如果正在執行的是Natvie方法,這個計數器值則爲空(Undefined)。此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。


2.Java虛擬機棧

棧幀是方法運行期的基礎數據結構棧容量可由-Xss參數設定

  • 與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
  • 常有人把Java內存區分爲堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java內存區域的劃分實際上遠比這複雜。其中所指的“堆”在後面會專門講述,而所指的“棧”就是現在講的虛擬機棧,或者說是虛擬機棧中的局部變量表部分。
  • 局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。主要存放了編譯期可知的各種基本數據類型、對象引用(reference類型)、returnAddress類型


3.本地方法棧

棧容量可由-Xss參數設定

虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。


4.Java堆

可通過參數 -Xms 和-Xmx設置  。

Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。


5.方法區

參數-XX:MaxPermSize可設置 .

方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。


6.運行時常量池

可以通過-XX:PermSize和-XX:MaxPermSize設置

  • 運行時常量池(Runtime Constant Pool)是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。
  • 運行時常量池相對於Class文件常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。


7.直接內存

可通過-XX:MaxDirectMemorySize指定,如果不指定,則默認與Java堆的最大值(-Xmx指定)一樣

直接內存(Direct Memory)並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是這部分內存也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現


三、HotSpot虛擬機對象探祕

主要探討HotSpot虛擬機在Java堆中對象分配、佈局和訪問的全過程。


1.對象的創建

虛擬機遇到new指令時,

  • 首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並且檢查引用代表的類是否已被加載、解析和初始化過。如果沒有,則執行類加載過程(第7章)。
  • 加載檢查通過後,分配內存(內存在類加載完成後便可完全確定)。
  • 內存分配完成後,虛擬機對對象進行必要的設置,如對象是哪個類的實例、如何找到類的元數據信息等(都放在對象的對象頭中)。
  • 從虛擬機角度看,一個新的對象產生了,但從java程序視角看,對象創建纔剛剛開始,因爲<init>方法還沒有執行,,所有字段爲零。執行new指令之後會接着執行<init>方法(構造方法),進行初始化,這樣一個真正可用的對象纔算完成產生。


2.對象的內存佈局

分爲對象頭、實例數據、對齊填充三部分。

1)對象頭

包含兩部分

  • 存儲對象自身的運行時數據,如哈希碼、GC分代年齡等。長度在32位和64位的虛擬機中,分別爲32bit、 64bit,官方稱它爲“Mark Word”
  • 類型指針,對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

注:如果對象是一個java數組,對象頭中還必須有一塊記錄數據長度的數據。

2)實例數據

對象真正存儲的有用信息,也是程序中定義的各種類型的字段內容。

3)對齊填充

由於HotSpot虛擬機要求對象的起始地址必須是8字節的整數倍,通俗的說,就是對象大小必須是8字節的整數倍。對象頭正好是8字節的倍數。當實例數據部分沒有對齊時,需要通過對齊填充來補全。


3.對象的訪問定位

對象訪問在Java語言中無處不在,是最普通的程序行爲,但即使是最簡單的訪問,也會卻涉及Java棧、Java堆、方法區這三個最重要內存區域之間的關聯關係,如下面的這句代碼:
   Object obj = new Object();
 假設這句代碼出現在方法體中,那“Object obj”這部分的語義將會反映到Java棧的本地變量表中,作爲一個reference類型數據出現。

而“new Object()”這部分的語義將會反映到Java堆中,形成一塊存儲了Object類型所有實例數據值(Instance Data,對象中各個實例字段的數據)的結構化內存
由於reference類型在Java虛擬機規範裏面只規定了一個指向對象的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的對象的具體位置,因此不同虛擬機實現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄和直接指針。



四、總結

最後上一張本章結構圖:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章