JVM运行时数据区和各个区域的作用
一、JVM主要分為5個核心區域(6個子區域),分別是:
二、各個區域作用和描述
| 1 | 程序計數器 | 線程私有 | 記錄當前線程鎖執行的字節碼行號指示器。 | Java虛擬機規范中唯一一個沒有規定OutOfMemoryError(內存不足錯誤)的區域。 | -- |
| 2 | Java虛擬機棧 | 線程私有 | 存放局部變量表、操作數據棧、動態鏈接、方法出口等信息。 | 棧深大于允許的最大深度,拋出StackOverflowError(棧溢出錯誤)。 內存不足時,拋出OutOfMemoryError(內存不足錯誤)。 | 常說的“棧”說的就是Java虛擬機棧,或者是Java虛擬機棧中的局部變量表。 |
| 3 | 本地方法棧 | 線程私有 | 和Java虛擬機棧類似,不過是為JVM用到的Native方法服務。 | 同上 | -- |
| 4 | Java堆 | 線程共享 | 存放實例化數據。 | 內存不足時,拋出OutOfMemoryError(內存不足錯誤)。 | 通過-Xmx和-Xms控制大小。 GC的主要管理對象。 |
| 5 | 方法區 | 線程共享 | 存放類信息(版本、字段、方法、接口等)、常量、靜態變量、即時編譯后的代碼等數據。 | 內存不足時,拋出OutOfMemoryError(內存不足錯誤)。 | -- |
| 6 | 運行時常量池 | 線程共享 | 存放編譯期生成的各種字面量和符號引用。 | 內存不足時,拋出OutOfMemoryError(內存不足錯誤)。 | 屬于“方法區”的一部分。 |
| 7 | 直接內存 | -- | 如NIO可以使用Native函數庫直接分配堆外內存,該內存受計算機內存限制。 | 內存不足時,拋出OutOfMemoryError(內存不足錯誤)。 | 不是JVM運行時數據區的一部分,也不是JVM虛擬機規范中定義的內存區域。但這部分內存也被頻繁的使用。所以放到一起。 |
*參考《深入理解Java虛擬機 JVM高級特性與最佳實踐》一書
JVM在執行JAVA程序時會把它管理的內存區域劃分為若干個不同的數據區域,統稱為運行時數據區,由圖可見JVM程序所占的內可劃分成5個部分:程序計數器、虛擬機棧(線程棧)、本地方法棧、堆(heap)和方法區(內含常量池),其中方法區和堆被所有線程共享。下面分別介紹各部分的功能:
程序計數器
JVM是多線程的,每一個線程都有一個獨立的程序計數器(JVM是多線程的,為了線程切換后能恢復到正確的執行位置),是一塊較小的內存空間,它與線程共存亡。JVM中的程序計數器指向的是正在執行的字節碼地址,可以看作是當前線程所執行的字節碼的行號指示器。
如果線程正在執行的是一個java方法,程序計數器記錄的是正在執行的虛擬機字節碼指令地址;
若線程執行的是Native方法,程序計數器則為Undefined。
程序計數器是JVM中唯一一個沒有規定任何OutOfMemoryError情況的區域。
Java虛擬機棧
一個線程一個棧,并且生命周期與線程相同。它的內部由一個個棧幀構成,一個棧幀代表一個調用的方法,線程在每次方法調用執行時創建一個棧幀然后壓棧,棧幀用于存放局部變量、操作數、動態鏈接、方法出口等信息。方法執行完成后對應的棧幀出棧。我們平時說的棧內存就是指這個棧。
一個線程中的方法可能還會調用其他方法,這樣就會構成方法調用鏈,而且這個鏈可能會很長,而且每個線程都有方法處于執行狀態。對于執行引擎來說,只有活動線程棧頂的棧幀才是有效的,稱為當前棧幀(Current Stack Frame),這個棧幀關聯的方法稱為當前方法(Current Method)。
棧幀的大致結構如下圖所示:
每一個棧幀的結構都包括了局部變量表、操作數棧、方法返回地址和一些額外的附加信息。某個方法的棧幀需要多大的局部變量表、多深的操作數棧都在編譯程序時完全確定了,并且寫入到類方法表的相應屬性中了,因此某個方法的棧幀需要分配多少內存,不會受到程序運行期變量數據變化的影響,而僅僅取決于具體虛擬機的實現。
局部變量區域:存儲方法的局部變量和參數,存儲單位以slot(4 byte)為最小單位。局部變量存放的數據類型有:基本數據類型、對象引用和return address(指向一條字節碼指令的地址)。其中64位長度的long和double類型的變量會占用2個slot,其它數據類型只占用1個slot。
類的靜態方法和對象的實例方法被調用時,各自棧幀對應的局部變量結構基本類似。但有以下如圖示區別:實例方法中第一個位置存放的是它所屬對象的引用,而靜態方法則沒有對象的引用。另外靜態方法里所操作的靜態變量存放在方法區。
void test(Object object){int i=0;Boolean b=false;} static void test1(int i ,Object object,boolean b){...}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
關于局部變量表,還有一點需要強調,就是局部變量不像類的實例變量那樣會有默認初始化值。所以局部變量需要手工初始化,如果一個局部變量定義了但沒有賦初始值是不能使用的。
操作數棧:?所謂操作數是指那些被指令操作的數據。當需要對參數操作時如c=a+b,就將即將被操作的參數數據壓棧,如將a 和b 壓棧,然后由操作指令將它們彈出,并執行操作。虛擬機將操作數棧作為工作區。Java虛擬機沒有寄存器,所有參數傳遞、值返回都是使用操作數棧來完成的。
Java虛擬機的解釋執行引擎稱為“基于棧的執行引擎”,其中所指的“棧”就是操作數棧。
例如:
public static int add(int a,int b){int c=0;c=a+b;return c;}add(25,23);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
主要操作步驟:
壓棧步驟:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
動態連接:它是個指向運行時常量池中該棧幀所屬方法的引用,這個引用是為了支持方法調用過程中能進行動態連接。我們知道Class文件的常量池存有方法的符號引用,字節碼中的方法調用指令就以指向常量池中方法的符號引用為參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化為直接引用,這種轉化稱為靜態解析。余下部分將在每一次運行期間轉化為直接引用,這部分稱為動態連接。
方法返回地址:
正常退出,執行引擎遇到方法返回的字節碼,將返回值傳遞給調用者;
異常退出,遇到Exception,并且方法未捕捉異常,返回地址由異常處理器來確定,并且不會有任何返回值。
方法退出的過程實際上等同于把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值(如果有的話)壓入調用者棧幀的操作數棧中,調整PC計數器的值以指向方法調用指令后面的一條指令等。
額外附加信息:虛擬機規范沒有明確規定,由具體虛擬機實現。
Java虛擬機規范規定該區域有兩種異常:
StackOverFlowError:當線程請求棧深度超出虛擬機棧所允許的深度時拋出
OutOfMemoryError:當Java虛擬機動態擴展到無法申請足夠內存時拋出
另外需要提醒一下,在規范模型中,棧幀相互之間是完全獨立的。但在大多數虛擬機的實現里都會做一些優化處理,這樣兩個棧幀可能會出現一部分重疊。這樣在下面的棧幀會有部分操作數棧與上面棧幀的部分局部變量表重疊在一起,這樣在進行方法調用時就可以有部分數據共享,而無須進行額外的參數復制傳遞了。具體情形如下圖所示:
本地方法棧
Java可以通過java本地接口JNI(Java Native Interface)來調用其它語言編寫(如C)的程序,在Java里面用native修飾符來描述一個方法是本地方法。本地方法棧就是虛擬機線程調用Native方法執行時的棧,它與虛擬機棧發揮類似的作用。但是要注意,虛擬機規范中沒有對本地方法棧作強制規定,虛擬機可以自由實現,所以可以不是字節碼。如果是以字節碼實現的話,虛擬機棧本地方法棧就可以合二為一,事實上,OpenJDK和SunJDK所自帶的HotSpot虛擬機就是直接將虛擬機棧和本地方法棧合二為一的。
Java虛擬機規范規定該區域也可拋出StackOverFlowError和OutOfMemoryError。
堆
這個區域用來放置所有對象實例以及數組,不過在JIT(Just-in-time)情況下有些時候也有可能在棧上分配對象實例。堆也是java垃圾收集器管理的主要區域(所以很多時候會稱它為GC堆),被所有線程共享。
從GC回收的角度看,由于現在GC基本都是采用的分代收集算法,所以堆內存結構還可以分塊成:新生代和老年代;再細一點的有Eden空間、From Survivor空間、To Survivor空間等。如下圖:
對象在堆內分配內存的兩種方法:
為對象分配空間的任務等同于把一塊確定大小的內存從Java堆中劃分出來。
指針碰撞(Serial、ParNew等帶Compact過程的收集器)
假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。
空閑列表(CMS這種基于Mark-Sweep算法的收集器)
如果Java堆中的內存并不是規整的,已使用的內存和空閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。
選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時,通常采用空閑列表。
方法區
它是虛擬機在加載類文件時,用于存放已加載的類的類信息,常量,靜態變量,及jit編譯后的代碼(類方法)等數據的內存區域,是線程共享的。
方法區存放的信息包括:
1.類的基本信息:
每個類的全限定名
每個類的直接超類的全限定名(可約束類型轉換)
該類是類還是接口
該類型的訪問修飾符
直接超接口的全限定名的有序列表
2.已裝載類的詳細信息:
3.運行時常量池:
類信息除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量、符號引用,文字字符串、final變量值、類名和方法名常量,這部分內容將在類加載后存放到方法區的運行時常量池中。它們以數組形式訪問,是調用方法、與類聯系及類的對象化的橋梁。
這里再講一下,JDK1.7之前運行時常量池是方法區的一部分,JDK1.7及之后版本已經將運行時常量池從方法區中移了出來,在堆(Heap)中開辟了一塊區域存放運行時常量池。
運行時常量池除了存放編譯期產生的Class文件的常量外,還可存放在程序運行期間生成的新常量,比較常見增加新常量方法有String類的intern()方法。String.intern()是一個Native方法,它的作用是:如果運行時常量池中已經包含一個等于此String對象內容的字符串,則返回常量池中該字符串的引用;如果沒有,則在常量池中創建與此String內容相同的字符串,并返回常量池中創建的字符串的引用。不過JDK7的intern()方法的實現有所不同,當常量池中沒有該字符串時,不再是在常量池中創建與此String內容相同的字符串,而改為在常量池中記錄堆中首次出現的該字符串的引用,并返回該引用。
4.字段信息:
字段信息存放類中聲明的每一個字段(實例變量)的信息,包括字段的名、類型、修飾符。
如private String a=“”;則a為字段名,String為描述符,private為修飾符。
5.方法信息:
類中聲明的每一個方法的信息,包括方法名、返回值類型、參數類型、修飾符、異常、方法的字節碼。(在編譯的時候,就已經將方法的局部變量表、操作數棧大小等完全確定并存放在字節碼中,在加載載的時候,隨著類一起裝入方法區。)
在運行時,虛擬機線程調用方法時從常量池中獲得符號引用,然后在運行時解析成方法的實際地址,最后通過常量池中的全限定名、方法和字段描述符,把當前類或接口中的代碼與其它類或接口中的代碼聯系起來。
5.靜態變量:
就是類變量,被類的所有實例對象共享,我們只需知道,在方法區有個靜態區,靜態區專門存放靜態變量和靜態塊。
6.到類ClassLoader的引用:到該類的類裝載器的引用。
7.到類Class的引用:虛擬機為每一個被裝載的類型創建一個Class實例,用來代表這個被裝載的類。
Java虛擬機規范規定該區域可拋出OutOfMemoryError。
直接內存
直接內存(Direct Memory)雖然不是程序運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,但這部分內存也被頻繁使用,而且它也可能導致OutOfMemoryError異常出現。
在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native方法庫直接分配堆外內存,然后通過一個存儲在Java堆里面的DirecByteBuffer對象作為這塊內存的引用進行操作。這樣能在某些應用場景中顯著提高性能,因為它避免了在Java堆和Native堆中來回復制數據。
顯然,本機直接內存的分配不會受到Java堆大小的限制,但是,還是會受到本機總內存(包括RAM及SWAP區或者分頁文件)的大小及處理器尋址空間的限制,從而導致動態擴展時出現OutOfMemoryError異常。
總結
在程序運行時類是在方法區,實例對象本身在堆里面。
方法字節碼在方法區。線程調用方法執行時創建棧幀并壓棧,方法的參數和局部變量在棧幀的局部變量表。
對象的實例變量和對象一起在堆里,所以各個線程都可以共享訪問對象的實例變量。
靜態變量在方法區,所有對象共享。字符串常量等常量在運行時常量池。
各線程調用的方法,通過堆內的對象,方法區的靜態數據,可以共享交互信息。
各線程調用的方法所有參數傳遞、方法返回值的返回,都是使用棧幀里的操作數棧來完成的。
?
-------------------------------------------------------------------------------------------------------------------------------
3.1.1 程序計數器
Program Counter Register
內存空間小,線程私有.它可以看做是當前線程所執行的字節碼的行號指示器.也就是說,線程主要是執行任務,而執行到哪里,需要使用程序計數器來記錄.字節碼解釋器工作是就是通過改變這個計數器的值來選取下一條需要執行指令的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴計數器完成.
由于java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,所以,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,所以我們說,它是線程私有的.
如果線程正在執行一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是 Native 方法,這個計數器的值則為 (Undefined)。此內存區域是唯一一個在 Java 虛擬機規范中沒有規定任何 OutOfMemoryError 情況的區域。
3.1.2 虛擬機棧
java virtual Machine Stacks
線程私有,生命周期和線程一致。描述的是 Java 方法執行的內存模型:每個方法在執行時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行結束,就對應著一個棧幀從虛擬機棧中入棧到出棧的過程。
在編譯程序代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,因此一個棧幀需要分配多少內存,不會受程序運行時期變量數據的影響.
一個線程中的方法調用鏈可能會很長,很多方法都處于同時執行狀態.對于執行引擎來說,在活動線程中,只有位于棧頂的棧幀才是有效的,執行引擎運行的所有的字節碼指令都只針對當前棧幀來進行操作的.
局部變量表
局部變量表是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量.
操作數棧
Operand Stack
是一個后進先出棧.其最大深度在編譯的時候已經確定了.當一個方法剛剛開始執行的時候,這個方法的操作數占是空的,在方法的執行過程中,會有各種字節碼指令往操作數占中寫入和提取內容,這就是出棧/入棧動作.
另外,在概念模型中,兩個棧幀作為虛擬機棧的元素,是完全相互獨立的,但大多數虛擬機的實現都會做一些優化處理,讓兩個棧幀出現部分重疊,讓下面的棧幀的部分操作數棧與上面棧幀的部分局部變量表重疊在一起,這樣在進行方法調用的時候就可以共用一部分數據.無須進行額外的參數的復制傳遞.
image
動態連接
每一個棧幀都包含一個執行運行時常量池中該棧幀所屬方法的引用.持有這個引用是為了支持方法調用過程中的動態連接.
這個引用是一個符號引用,不是方法實際運行的入口地址,需要動態的找到具體的方法入口.
這個特性給java帶來了更強大的動態擴展能力,但也使得java方法調用過程變得相對復雜起來,需要在類加載期間,甚至到運行期間才能夠確定目標方法的直接引用.
方法返回地址
正常完成出口:方法正確執行,執行引擎遇到方法返回的指令,回到上層的方法調用者.
異常完成出口:方法執行過程中發生異常,并且沒有處理異常,這樣是不會給上層調用者產生任何返回值.
方法正常退出,將會返回程序結束其的值給上層方法,經過調整之后以指向方法調用指令后面的一條指令,繼續執行上層方法.
3.1.3 本地方法棧
Native Method Stack
區別于Java 虛擬機棧的是,Java 虛擬機棧為虛擬機執行 Java 方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的Native 方法服務。也會有 StackOverflowError 和 OutOfMemoryError 異常。
3.1.4 堆
heap
對于絕大多數應用來說,這塊區域是 JVM 所管理的內存中最大的一塊。線程共享,主要是存放對象實例和數組。內部可以設置劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer, TLAB)。可以位于物理上不連續的空間,但是邏輯上要連續。
從內存回收的角度來看,由于現在收集器基本上都采用分代收集算法,所以對空間還可以細分為:新生代(年輕代),老年代(年老代).再細致一點,可以分為Eden空間,From Survivor空間, To Survivor空間.
不論如何劃分,都與存放內容無關,都是存放的是對象實例,進一步劃分的目的是為了更好的回收內存,或者更快的分配內存.
3.1.5 方法區
Method Area
屬于共享內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
對于習慣在HotSpot虛擬機上開發和部署程序的開發者來說,很多人愿意把方法區稱為“永久代”(Permanent Generation)(java8之前,使用永久代來實現方法區,在java8之后,廢除永久代,將字符串常量池移動到堆中,并新增Meta space,直接在系統內存中.),本質上兩者并不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。
Java虛擬機規范對這個區域的限制非常寬松,除了和Java堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但并非數據進入了方法區就如永久代的名字一樣“永久”存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說這個區域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當苛刻,但是這部分區域的回收確實是有必要的。
3.1.6 運行時常量池
Runtime Constant Pool
屬于方法區一部分,用于存放編譯期生成的各種字面量和符號引用。內存有限,無法申請時拋出 OutOfMemoryError.
3.1.7 直接內存
Direct Memory
非虛擬機運行時數據區的部分.
在 JDK 1.4 中新加入 NIO (New Input/Output) 類,引入了一種基于通道(Channel)和緩存(Buffer)的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。可以避免在 Java 堆和 Native 堆中來回的數據耗時操作。
本機直接內存的分配不會受到java堆大小的限制,但是,既然是內存,肯定還是會受到本機總內存大小的限制.所以我們在配置虛擬機參數時,不要忽略直接內存,否則可能因為動態擴展導致出現OutOfMemoryError.
3.2 基于棧的執行過程分析
下面我們通過一個小程序,來分析一下,虛擬機中實際是如何執行代碼的.
public int calc(){int a = 100;int b = 200;int c = 300;return (a + b) * c;}代碼非常簡單,我們可以直接使用javap命令(javap -c CalcTest.class > calc.txt),通過反匯編操作,來查看對應的字節碼指令.
image
查閱虛擬機字節碼指令表,我們先將上面的反匯編代碼翻譯一下:
image
我們從這段程序的執行中,也可以回過來再次認識棧結構.整個過程的中間變量都是以操作數棧的出棧,入棧為信息交換途徑.
3.3 HotSpot虛擬機對象探秘
堆,是我們最實用的一塊內存空間,分析完棧幀的執行過程之后,現在我們再來分析一下,虛擬機在java堆中對象的創建,布局和訪問的過程.
3.3.1 對象的創建
Java是一門面向對象的語言,在運行過程中無時無刻都有對象的創建,在語言層面,僅僅是一個關鍵字new,那么在虛擬機中,對象是如何創建出來的呢?
① 檢查類是否已經被加載:虛擬機遇到 new 指令時,首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,執行相應的類加載。
② 為新生對象分配內存:類加載檢查通過之后,為新對象分配內存(內存大小在類加載完成后便可確認)。
如果堆內存絕對規整,使用指針碰撞.否則使用空閑列表,找到一塊足夠大的內存劃分給對象實例.
image
堆內存是否規整,主要是看GC回收了內存之后是否包含壓縮或者整理功能.如果有,那么內存就比較規整.否則如果沒有,創建對象就需要采用空閑列表的方式.
比如:
serial,ParNew等帶有整理的收集器,可以使用指針碰撞.
CMS使用簡單清除的算法,可以使用空閑列表.
如果線程支持在堆中都有私有的分配緩沖區(TLAB),這樣可以很大程度避免在并發情況下頻繁創建對象造成的線程不安全。
③ 內存空間分配完成后會將整個空間都初始化為零值(不包括對象頭).
④ 接下來就是填充對象頭,把對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息存入對象頭。
⑤ 執行 new 指令后,執行 init 方法(由字節碼指令invokespecial決定,執行初始化方法)后,才算一份真正可用的對象創建完成.
3.3.2 對象的內存布局
在上文中,我們講到一個步驟是,填充對象頭.那什么是對象頭呢?
在 HotSpot 虛擬機中,對象在內存中存儲的布局可以分為3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding).
對象頭(Header):
包含兩部分,第一部分用于存儲對象自身的運行時數據,如哈希碼、GC 分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳等,根據不同的系統為數固定大小.官方稱為 ‘Mark Word’。第二部分是類型指針,即對象指向它的類的元數據指針,虛擬機通過這個指針確定這個對象是哪個類的實例。另外,如果是 Java 數組,對象頭中還必須有一塊用于記錄數組長度的數據,因為普通對象可以通過 Java 對象元數據確定大小,而數組對象不可以。
實例數據(Instance Data):程序代碼中所定義的各種類型的字段內容(包含父類繼承下來的和子類中定義的)。
對齊填充(Padding):不是必然需要,主要是占位,保證對象大小是某個字節的整數倍。
3.3.3 對象的訪問定位
使用對象時,通過棧上的 reference 數據來操作堆上的具體對象。
由于java虛擬機只規定要一個執行對象的引用,而沒有規定以何種方式去定位.所以對象訪問方式取決于虛擬機的實現.主流的方式有兩種:
1.通過句柄訪問.Java 堆中會分配一塊內存作為句柄池。reference 存儲的是句柄地址.
2.使用指針訪問.reference 中直接存儲對象地址.
比較:使用句柄的最大好處是 reference 中存儲的是穩定的句柄地址,在對象移動(GC)是只改變實例數據指針地址,reference 自身不需要修改。直接指針訪問的最大好處是速度快,節省了一次指針定位的時間開銷。如果是對象頻繁 GC 那么句柄方法好,如果是對象頻繁訪問則直接指針訪問好.
HotSpot使用第二種方式進行對象訪問的.
?
?
?
總結
以上是生活随笔為你收集整理的JVM运行时数据区和各个区域的作用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis实现消息队列的4种方案
- 下一篇: docker环境安装,镜像和容器常用命令