JVM内存区域详解
Java中虛擬機在執行Java程序的過程中會將它所管理的內存區域劃分為若干不同的數據區域。下面來介紹幾個運行時數據區域。
一、程序計數器
1.1 簡述
程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。
1.2 作用
大家都知道,Java程序從源文件創建到程序運行要經過兩大步驟: 1. 源文件由編譯器編譯成字節碼(ByteCode)。 2. 字節碼由java虛擬機解釋運行。
java程序編譯運行過程
字節碼解釋器工作時就是通過改變這個 程序計數器 的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。
1.3 特性
1.3.1 線程私有情況
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。
1.3.2 異常情況
此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
二、Java虛擬機棧
2.1 描述
大家經常說的棧內存(Stack)就是指的虛擬機棧。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(Stack Frame),用于存儲局部變量表、操作數棧、動態鏈表、方法出口等信息。
不同的線程使用不同的棧
每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中出棧入棧的過程。
2.2 組成
2.2.1 局部變量表
局部變量表是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量。它可以存儲各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型)和returnAddress。 1. reference類型:虛擬機規范沒有明確說明它的長度。它不等同于對象本身,根據虛擬機種類的不同,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置。 2. returnAddress:指向了一條字節碼指令的地址,它是為字節碼指令jsr、jsr_w和ret服務的。目前returnAddress類型已經很少見了,很古老的java虛擬機曾經使用這幾條指令來實現異常處理,現在已經由異常表代替。
注意: 系統不會為局部變量賦予初始值(實例變量和類變量都會被賦予初始值)。也就是說不存在類變量那樣的準備階段。
2.2.2 操作數棧
操作數棧中存放方法執行時的一些中間變量,JVM 在執行方法時壓入或者彈出這些變量。其實,操作數棧是方法真正工作的地方,執行方法時,局部變量數組與操作數棧根據方法定義進行數據交換。 例如,執行以下代碼時,它演示了虛擬機是如何把兩個int類型的局部變量相加,再把結果保存到第三個局部變量的。操作數棧的情況如下:
int a = 90; int b = 10; int c = a + b;字節碼執行過程:
begin iload_0 // push the int in local variable 0 onto the stack iload_1 // push the int in local variable 1 onto the stack iadd // pop two ints, add them, push result istore_2 // pop int, store into local variable 2 end在這個字節碼序列里,前兩個指令iload_0和iload_1將存儲在局部變量中索引為0和1的整數壓入操作數棧中,其后iadd指令從操作數棧中彈出那兩個整數相加,再將結果壓入操作數棧。第四條指令istore_2則從操作數棧中彈出結果,并把它存儲到局部變量區索引為2的位置。下圖詳細表述了這個過程中局部變量和操作數棧的狀態變化,圖中沒有使用的局部變量區和操作數棧區域以空白表示。
注意在這個圖中,操作數棧的頂部是在底邊,所以先壓入的100位于上方。可以看出,操作數棧其實是一個數據臨時存儲區,存放一些中間變量,方法結束了,操作數棧也就沒有啦。
2.2.3 動態鏈表
略,待續
2.3 特性
2.3.1 線程私有情況
與程序計數器一樣,Java 虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同
2.3.2 異常情況
三、本地方法棧
3.1 描述
本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,其 區別 不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的Native方法服務。 虛擬機規范中對本地方法棧中的方法使用的語言、使用方式與數據結構并沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。
3.2 特性
3.2.1 線程私有情況
和虛擬機棧一樣。
3.2.2 異常情況
與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
四、Java堆
4.1 描述
對于大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。
4.2 作用
4.3 特性
4.3.1 線程私有情況
Java堆是被所有線程共享的一塊內存區域,可以被線程之間共享。
4.3.2 異常情況
Java堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可,就像我們的磁盤空間一樣。在實現時,既可以實現成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(通過-Xmx和-Xms控制)。 如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出`OutOfMemoryError`異常。
五、方法區
5.1 描述
同 Java 堆一樣,方法區也是全局共享的一塊內存區域。
5.2 作用
5.3 特性
5.3.1 線程私有情況
與Java堆一樣,是各個線程共享的內存區域。
5.3.2 異常情況
與java堆類似,不需要連續的內存和可以選擇固定大小或可拓展外,還可以選擇不實現垃圾回收。 根據虛擬機規范的規定,當方法區無法滿足內存分配的需求時,將拋出OutOfMemoryError異常。
5.4 運行時常量池
運行時常量池(Runtime Constant Pool)也是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分將在類加載后進入方法區運行時常量池中存放。
5.4.2 特性
六、直接內存
6.1 描述
直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,它指的是主機的物理內存。之所以拿到這里來說,是因為可能會導致OutOfMemoryError異常出現,所以放到這里一并講解。
6.2 如何使用?
那么什么時候會直接使用到物理內存呢? 在NIO(New Input/Output)類中,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/0方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回復制數據。
6.3 異常情況
當各個內存區域總和大于物理內存限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現OutOfMemoryError異常。
七、總結
最后,讓我們通過表格來大致回顧一下Java虛擬機中內存的特性吧!
| 程序計數器 | 線程私有 | 唯一一個沒有異常情況的區域 | 當前線程所執行字節碼的行號指示器 |
| Java虛擬機棧 | 線程私有 | StackOverflowError或OutOfMemoryError異常 | 用于存儲局部變量表、操作數棧、動態鏈表、方法出口等信息,為虛擬機棧執行Java方法服務 |
| 本地方法棧 | 線程私有 | StackOverflowError或OutOfMemoryError異常 | 為虛擬機使用到的Native方法(字節碼)服務 |
| Java堆 | 線程共享 | OutOfMemoryError異常 | 存放對象實例和主要的垃圾回收區域 |
| 方法區 | 線程共享 | OutOfMemoryError異常 | 用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。和Java堆有相似特性,但不是垃圾回收的主要區域 |
| 運行時常量池 | 線程共享 | OutOfMemoryError異常 | 方法區的一部分,和方法區特性類似。保存了Class文件中描述的符號引用和翻譯出來的直接引用,同時具有動態性。 |
| 直接內存 | 線程共享 | OutOfMemoryError異常 | 受限于物理內存,NIO流可以直接操縱用以提高性能 |
參考鏈接: 1. 棧幀、局部變量表、操作數棧 2. Java編譯器、JVM、解釋器 3. Java程序編譯和運行的過程
總結
- 上一篇: 45岁没有交过养老保险,是一次性补缴好还
- 下一篇: 使用 rqt_console 和 ros