一、java虛擬機內存區域


內存區域

  java虛擬機在java程序的過程中會把它所管理的內存划分為若干個不同的數據區域。java虛擬機規范將JVM管理的內存分為:程序計數器、本地方法棧、Java虛擬機棧、方法區、Java堆。如下圖:

 1、程序計數器

  一塊較小的內存空間,可以看做是當前線程執行字節碼文件的行號指示器。字節碼解釋器通過改變計數器的值選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等都需要依賴程序計數器來完成。

  java虛擬機的多線程是通過線程輪流切換並分配處理器執行時間來完成的,在任何一個確定的時刻,一個處理器(多核處理器來說是一個內核)都只會執行一個線程中的指令。因此,為了線程切換后能恢復到正確的執行的位置,每個線程都需要有一個獨立的程序計數器,各線程間的計數器互不影響,獨立存儲。所以該區域是線程私有的。

  如果線程正在執行的是一個java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法(本地操作系統方法),這個計數器的值為空(Undefined)。該內存區域是唯一一個在java虛擬機規范中沒有任何OutOfMemoryError情況的區域。 

2、java虛擬機棧

  與程序計數器一樣,該內存區域也是線程私有的。java虛擬機棧描述的是java方法執行的內存模型,每個方法被執行的時候都會同時創建一個棧幀,它是用於支持虛擬機進行方法調用和方法執行的數據結構。對於執行引擎來說,活動的線程中,只有棧頂的棧幀是有效的,這個棧幀所關聯的方法稱為當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。棧幀用於存儲局部變量表、操作數棧、動態連接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,並且寫入了方法表的Code屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。

在Java虛擬機規范中,對這個區域規定了兩種異常情況:

    1) 如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常。

    2) 如果虛擬機在動態擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。

    這兩種情況存在着一些互相重疊的地方:當棧空間無法繼續分配時,到底是內存太小,還是已使用的棧空間太大,其本質上只是對同一件事情的兩種描述而已。在單線程的操作中,無論是由於棧幀太大,還是虛擬機棧空間太小,當棧空間無法分配時,虛擬機拋出的都是StackOverflowError異常,而不會得到OutOfMemoryError異常。而在多線程環境下,則會拋出OutOfMemoryError異常。

以下是棧幀中存放的各類信息的詳細介紹。

2.1、局部變量表

  局部變量表示一組變量的存儲空間,存儲的是方法參數和方法內部定義的局部變量。其中存放的類型是編譯期可知的各種基本數據類型,對象引用(reference)和returnAddress(指向一條字節碼指令的地址)。局部變量表所需的空間在程序編譯期間就已經確定,即在java文件編譯成class文件的時候就已經確定了最大局部變量表的容量。當程序執行到一個方法的時候,這個方法需要在棧中分配多大的局部變量空間就已經完全確定了,在方法運行期間不會改變局部變量表的大小。

  局部變量表的容量以變量槽(Slot)為單位。在虛擬機規范中沒有明確指明一個Slot所占用內存空間(允許其隨着處理器、操作系統或虛擬機的不同而發生變化),一個Slot槽可以存放一個32位以內的數據類型,boolean、byte、char、short、int、float、reference和returnAddresss。reference是對象引用類型,returnAddress為字節碼指令jsr、jsr_w和wet服務的,它指向一條虛擬機指令的操作碼。對於64位的數據類型,虛擬機會以高位在前的方式為其分配兩個連續的Slot空間。java語言目前規定的64位的數據類型只有long和double兩種,reference類型則可能是32位有可能是64位。

  虛擬機通過索引定位的方式使用局部變量表,索引值從0開始到局部變量表的最大Slot數量。對於32位的數據類型,索引n代表第n個Slot槽,對於64位的數據類型,索引n代表第n和第n+1兩個Slot槽。

  在方法執行時,虛擬機是通過局部變量表來完成參數值到參數變量列表傳遞的。如果是實例方法(非static),局部變量表的第0位索引的Slot默認存儲實例對象的引用,在方法中可通過"this"關鍵字訪問這個參數。其余參數則是按照參數列表的順序排序的,從索引1開始的Slot開始存儲,當參數列表分配完畢,再根據方法內部定義的變量順序和作用域分配其余Slot。

  局部變量表中的Slot是可以重用的。在方法體中定義的變量,其作用域不一定涵蓋整個方法體,如果當前程序計數器的值已經超過某個變量的作用於,那么這個變量所對應的Slot就可以交給其他變量來使用了。這樣的設計不是為了節省空間,而是在某些情況下Slot的復用會直接影響系統的垃圾回收行為。

2.2、操作數棧

  操作數棧又被稱為操作棧,操作數棧的最大深度也是在程序編譯期間就確定的了,最大深度會被寫入到Code屬性的max_stacks數據項之中。32位數據占用的操作數棧容量是1,64位的數據占用的操作數棧容量為2。當一個方法開始執行的時候,它的操作數棧是空的,在方法執行中會有各種字節碼指令向操作數棧中寫入和提取內容,也就是出棧和入棧的操作。例如,在做算術運算的時候是通過操作數棧來進行的,又或者在調用其他方法的時候是通過操作數棧來進行參數傳遞的。

  java虛擬機的解釋執行引擎稱為"基於棧的執行引擎",其中所指的棧就是操作數棧,因此我們也稱java虛擬機是基於棧的。它和Android的虛擬機執行引擎不同,Android是基於寄存器的。

  基於棧的指令集最大的優點是可移植性強,主要缺點是效率會相對低一些。由於寄存器是硬件直接提供,因此基於寄存器的指令集優點是執行效率高,缺點是可移植性差。

2.3、動態連接

  每個棧幀中都包含一個指向運行時常量池中的該棧幀所屬方法的引用,持有這個引用是為了方便方法調用過程中的動態連接。Class文件的常量池中存在大量的符號引用,字節碼中的方法調用指令就是以常量池中指向方法的符號引用為參數,這些符號引用,一部分會在方法加載階段或者第一次調用的時候轉換為直接引用,如final和static域,成為靜態解析,另外一部分則會在每次運行期間轉換為動直接引用,這部分稱為動態連接。

2.4、方法返回地址

  當一個方法被執行后有兩種方式退出這個方法:

    1)執行引擎遇到任意一個方法返回的字節碼指令。

    2)遇到異常,並且這個異常沒有在方法體里得到處理。

  無論采用何種退出方式,在方法退出后,都需要返回方法被調用的位置,程序才能繼續執行。方法返回時可能需要在棧幀中保存一些信息,用於恢復它的上層方法的執行狀態。一般來說方法正常退出,調用者的PC計數器的值就可作為返回地址,棧幀中很可能保存的就是這個計數器的值。而方法異常退出時,返回地址要通過異常處理器來確定,這時候棧幀中一般不會保存這部分信息。 

  方法退出的過程實際上等同於將當前棧幀出棧。因此退出時的操作可能有:恢復上層方法的局部變量表和操作數棧,如果有返回值則把他壓入調用者棧幀的操作數棧中。跳幀PC計數器的值以指向方法調用的指令的后一條指令。

3、本地方法棧

   該區域與虛擬機棧所發揮的作用非常相似,只是虛擬機棧為虛擬機執行Java方法服務,而本地方法棧則為使用到的本地操作系統(Native)方法服務。

4、java虛擬機堆

  java虛擬機堆是java虛擬機所管理的內存中最大的一塊,他是所有線程共享的區域。幾乎所有的對象實例和數組都是在該區域進行內存分配的。java虛擬機堆是垃圾回收的主要區域,因此很多時候也稱為"GC堆"。

  根據java虛擬機規范,Java虛擬機堆可以使用物理上不連續的內存空間,只要是邏輯上連續的即可。如果在堆中沒有內存可分配時,並且堆也無法擴展時,將會拋出OutOfMemoryError異常。

5、方法區

  方法區也是線程共享的區域,主要用於存放已經被虛擬機加載的類信息、常量(jdk 1.7 后被移出方法區)、靜態變量、即使編譯器編譯后的代碼信息等。方法區又被稱為"永久代",只有HotSpot才有永久代。JRockit和IBM J9虛擬機中並不存在永久代的概念。java虛擬機規范把方法區描述為Java虛擬機堆的一部分,而且他和Java虛擬機堆一樣不需要物理上連續,大小可固定也可擴展。java虛擬機規范允許該區不進行內存回收。該區域內存回收主要針對廢棄常量(Jdk 7 后被移出方法區)和無用的類。運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法和接口外,還有一項信息是常量池(Class文件常量池),用於存放編譯器生成的字面量和符號引用,這部分內容將會在虛擬機加載后放入到方法區的運行時常量池中。運行時常量池相對於Class文件常量池的另一個特點是具備動態性,java語言並不要求常量一定是在編譯期間生成,也就是並非預置入Class常量池的內容才能進入方法區的運行時常量池,運行期間也可將新的常量加入到運行時常量池中,這種特性運用比較多的是String的intern()方法。

  根據Java虛擬機規范的規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

6、直接內存

  直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,它直接從操作系統中分配,因此不受Java堆大小的限制,但是會受到本機總內存的大小及處理器尋址空間的限制,因此它也可能導致OutOfMemoryError異常出現。在JDK1.4中新引入了NIO機制,它是一種基於通道與緩沖區的新I/O方式,可以直接從操作系統中分配直接內存,即在堆外分配內存,這樣能在一些場景中提高性能,因為避免了在Java堆和Native堆中來回復制數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM