JVM記憶體結構---《深入理解Java虛擬機器》學習總結,深入理解jvm虛擬機器
Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些地區的用途各不相同,同時也依據著各自的執行規則,獨立的建立和銷毀資料。
虛擬機器記憶體的劃分,:
線程之間互相獨立的地區有:
虛擬機器棧 、本地方法棧、程式計數器
線程可以共用資料的地區:
方法區 、堆
每個地區的作用分別如下:
程式計數器 Program Counter Register:
眾所周知,虛擬機器處理多線程時,是通過輪流的切換線程,來擷取cpu的執行機會的。在虛擬機器執行程式的過程中,當線程執行到某一位置時,虛擬機器將cpu的執行機會出讓給了其他線程,此時原有線程的執行(防盜串連:本文首發自http://www.cnblogs.com/jilodream/ )位置需要被記錄下來,而新得到執行機會的線程,又需要提供上次執行的位置,以此來保證程式中的多個線程可以持續的並行的執行下去。
程式計數器的作用就是將各個線程下次所執行的(位元組碼)行號(準確來說是指令的地址)記錄下來,以保證其下次執行時可以正確的執行。
根據程式計數器的作用,我們可以知道:
1、每個線程都在這個地區中都應該擁有一個只為自己提供服務的程式計數器,它們之間是隔離儲存區 (Isolated Storage),互不影響的存在。
2、我們還可以知道,程式計數器只記錄位元組碼的行號,因此當線程執行本地方法(Native method)時,計數器的值是空。
3、程式計數器所耗費的記憶體空間非常小,因此這個地區是不會拋出OutOfMemoryError錯誤的。
虛擬機器棧 VM Stack:
線程想要正常的運行下去,單靠程式計數器來記錄行號是遠遠不止的。線程還需要擁有自己的Runspace,在這個空間中,虛擬機器可以儲存方法的執行順序、方法的內部局部變數,方法在運算時,所需要的記憶體空間等。
在資料結構中,棧的特性最滿足方法的進入返回的結構的。而這塊地區的主(防盜串連:本文首發自http://www.cnblogs.com/jilodream/ )要作用就是線程在執行java方法時所需要記錄的資料。因此我們將這塊地區稱之為虛擬機器棧。但是要記住這裡與我們在工作中通常指的棧並不等同,這個我會在後邊介紹。
虛擬機器棧的結構如下:
而對於每一個棧幀內部的劃分又是這樣的:
每一部分的作用如下:
(1)局部變數表:每一個方法都可以定義一個只屬於自己的局部變數,當這個方法運行結束後,這個局部變數的生命週期也就宣告結束。所以每一個方法都應該擁有一個塊屬於自己的記憶體地區用來儲存方法內部定義的局部變數。這塊地區就是局部變數表,我們平常工作中所指的棧,實際上指的是虛擬機器棧中的棧幀中的局部變數表。
(2)運算元棧:每個方法的內部都可以計算資料,而計算資料勢必需要擁有一塊記憶體地區,為虛擬機器用來進行數值計算。因此在棧幀中,就需要有一塊地區專門為當前方法計算資料使用,它就是運算元棧。
在每進行一次完整的計算之後,棧中的資料都已經出棧,所以運算元棧的空間在一個方法內部是可以反覆使用的。所以虛擬機器在分配記憶體大小時,只分配當前方法,單次完整計算所需要的最大記憶體空間給當前棧幀,以減少記憶體的消耗。
同時為了增加運行效率,減少資料的不斷複製,在大部分虛擬機器的實現中,將當前方法的局部變數表和上層方法的運算元棧的記憶體形成部分重疊,從而減少參數的不斷複製而引起的效能消費。(防盜串連:本文首發自http://www.cnblogs.com/jilodream/ )
(3)動態串連:
虛擬機器在執行方法時有兩種形式被用來確定執行指令所對應的方法,
第一種是類載入時,可以直接確定要執行的方法,譬如靜態方法,私人方法,final方法等。這種形式叫做靜態解析。
第二種是在真正運行時,根據對象的真實引用來判斷當前真正要執行的方法。這種形式稱之為動態串連。
在位元組碼檔案中,都存在一個常量池,在這個常量池中儲存有大量的符號引用,這個符號引用是每一個方法的間接引用。在位元組碼指令的中,使用的是這個符號引用。但是在運行時階段,肯定需要調用到要執行方法在記憶體中真實的地址。這就需要將間接引用轉化成直接引用。而這裡的“動態串連”就是為了保證在運行時階段,方法可以正確的找到要調用的方法,每個棧幀將自己在運行時常量池中所對應的真真實位址記錄的位置。
這裡需要注意的是,在棧幀中的動態串連和尋找符號引用為真實引用中的動態串連,是兩個概念。前者表示的是一個地區,後者表示的是一種尋找方式。
(4)返回地址:
退出當前方法的方式有兩種,第一種是遇到返回指令時,正常的退出當前方法。另一種形式是遇到沒有捕獲而被拋出的異常。無論何種返回形式,在方法退出後,棧幀的頂端都應是當前退出方法的上層方法。同時上層方法的執行狀態也需要根據當前的返回結果重新調整。所以每個棧幀可以利用“返回地址”這塊地區協助上層方法恢複狀態。
(5)附加資訊:對於虛擬機器規範中沒有申明的,擁有指定存放位置的資訊可以由各個虛擬機器自己決定,放置到這個地區中。
本地方法棧 Native Stack
在虛擬機器中,不但運行java方法,還會運行本地方法,也就是常見的Native 關鍵字修飾的方法。在虛擬機器棧中,會為每個線程獨立的開闢一個專門運行java語言(更準確的說應該是位元組碼)的方法(防盜串連:本文首發自http://www.cnblogs.com/jilodream/ )棧,但是對於本地方法,則是使用另外的一塊記憶體地區來儲存線程的調用狀態,這塊地區就是本地方法棧。他的作用跟虛擬機器棧基本相似,其區別就是一個為java方法服務,一個為Native發光法服務。在虛擬機器規範中,對於本地方法棧中的結構、方法的語言、方式,都沒有強制規定,各個虛擬機器可以自由的實現它。
Java堆 Java Heap
我們平常所說的,在堆中建立一個執行個體,指的就是這個堆。這是虛擬機器所管理的記憶體中最大的一塊。在虛擬機器中,幾乎所有的執行個體以及數組所分配的記憶體空間都會被放置在這個堆中。
由於java堆是對象執行個體的的主要存放位置,因此虛擬機器的記憶體回收機制的主要工作區域。
根據Java的記憶體回收機制,我們可以將堆的大小和內容劃分成如下的形式:
根據java堆的特性,我們也可以知道,這塊地區是一塊線程共用的地區。同時我們也可以看出來,這塊地區,所可以使用在物理上非連續的記憶體,只要在邏輯上保持連續即可。
方法區 Method Area
方法區的主要作用是儲存類資訊、常量、靜態變數以及即時編譯後的代碼等資料。這個地區中的資料仍然會被GC的代回收所涉及到。我們平常所說的永久代,指的就是這個地區。
儘管這個地區也被稱之為永久代,但是當資料進入這個地區中,仍然可能會被回收。這個地區的回收目標主要是常量池的回收,以及類型的卸載。
運行時常量池 Runtime Constant Pool
這塊地區屬於方法區的中的一塊子領域。
在Class檔案中,除了有類版本、欄位、方法、介面等,還有一個資訊區(防盜串連:本文首發自http://www.cnblogs.com/jilodream/ )域是常量池。常量池中的資料將會在類被載入後,存在到運行時常量池中。
而類檔案中的常量池主要包括各種字面量和符號引用。符號引用在講解棧幀時,有所涉及。
字面量可以理解為java語言中的常量,如字串、final修飾的變數等。
符號引用則是指以下三種固定資訊:
(1)類和介面的全限定名稱
(2)欄位的名稱和描述符
(3)方法的名稱和描述符
java語言在編譯成Class檔案後,並沒有關於方法和欄位在記憶體中最終布局的資訊。所以當虛擬機器使用這些變數或方法時,需要先從常量池中,找到這些資料對應的符號引用,然後在方法的棧幀中的動態串連地區中找到其對應的記憶體真實位置。
在日常工作中,我們經常會遇到兩種記憶體溢出的錯誤:
1、OutOfMemoryError
2、StackOverflowError
OutOfMemoryError指的是一個地區中,由於資料的不斷增加,導致地區無法再從實體記憶體總申請到更大的空間,或者是地區所申請的空間已經到達虛擬機器運行參數所給該地區設定的最大值,那麼就會拋出這個錯誤。
StackOverflowError則指的是記憶體中的棧結構在不斷的入棧,最終導致棧的深度超過了虛擬機器所允許的棧深度時,所拋出的錯誤。