Java
的運行時數據區域分爲五大數據區域。這些區域各有各的用途,建立及銷燬時間。以下圖所示,其中方法區和堆是全部線程共享的,棧,本地方法棧和程序虛擬機則爲線程私有的。
根據Java
虛擬機規範,Java
虛擬機管理的內存分爲方法區、堆、虛擬機棧、本地方法棧、程序計數器棧五大區域。算法
程序計數器是一塊很小的內存空間,它是線程私有的,能夠認做爲當前線程的行號指示器。segmentfault
在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。因爲Java
虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,對於一個處理器(若是是多核CPU
那就是一核)來講,在一個肯定的時刻只會執行一條線程中的指令,而一條線程中有多個指令,爲了線程切換能夠恢復到正確執行位置,每一個線程都需有獨立的一個程序計數器,不一樣線程之間的程序計數器互不影響,獨立存儲。數組
若是線程執行的是個Java
方法,那麼計數器記錄的是虛擬機字節碼指令的地址。若是爲native
底層方法,那麼計數器爲空(Undefined
)。這塊內存區域是虛擬機規範中惟一沒有OutOfMemoryError
的區域。數據結構
Java
棧同計數器也爲線程私有,生命週期相同,就是平時說的棧,棧描述的是Java
方法執行的內存模型。
每一個方法被執行的時候都會建立一個棧幀用於存儲局部變量表,操做棧,動態連接,方法出口等信息。每個方法被調用的過程就對應一個棧幀在虛擬機棧中從入棧到出棧的過程。多線程
棧幀(Stack Frame
): 是用來存儲數據和部分過程結果的數據結構。
棧幀的位置: 內存 -> 運行時數據區 -> 某個線程對應的虛擬機棧 ->here
棧幀大小肯定時間: 編譯期肯定,不受運行期數據影響。
一般有人將Java
內存區分爲棧(Heap
)和堆(Heap
),實際上Java
內存比這複雜,這麼區分多是由於咱們最關注的與對象內存分配關係最密切的是這兩個,平時說的棧通常就是指局部變量表。
局部變量表: 一片連續的內存空間,用來存放方法參數,以及方法內定義的局部變量,存放着編譯期間已知的數據類型(八大基本類型和對象引用(reference
類型),returnAddress
類型。它的最小的局部變量表空間單位爲Slot
,虛擬機沒有指明Slot
的大小,但在JVM
中,long
和double
類型數據明確規定爲64
位,這兩個類型佔2
個Slot
,其它基本類型固定佔用1
個Slot
。函數
reference
類型: 與基本類型不一樣的是它不等同自己,即便是String
,內部也是char
數組組成,它多是指向一個對象起始位置指針,也可能指向一個表明對象的句柄或其餘與該對象有關的位置。性能
returnAddress
類型: 指向一條字節碼指令的地址大數據
須要注意的是,局部變量表所須要的內存空間在編譯期完成分配,當進入一個方法時,這個方法在棧中須要分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表大小。優化
Java
虛擬機棧可能出現兩種類型的異常:spa
StackOverflowError
。OutOfMemory
異常。本地方法棧是與虛擬機棧發揮的做用十分類似,區別是虛擬機棧執行的是Java
方法(也就是字節碼),而本地方法棧則爲虛擬機使用到的native
方法服務,可能底層會調用C
或者C++
,咱們打開jdk
安裝目錄能夠看到也有不少用C
編寫的文件,可能就是native
方法所調用的C
代碼。
堆是Java
虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制。Java
堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一做用就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。
Java
虛擬機規範中的描述是: 全部的對象實例以及數組都要在堆上分配,可是隨着JIT
編譯器的發展與逃逸分析技術逐斷成熟,棧上分配、標量替換優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。
即時編譯器: 能夠把Java
的字節碼,包括須要被解釋的指令的程序轉換成能夠直接發送給處理器的指令的程序)逃逸分析: 經過逃逸分析來決定某些實例或者變量是否要在堆中進行分配,若是開啓了逃逸分析,便可將這些變量直接在棧上進行分配,而非堆上進行分配。這些變量的指針能夠被全局所引用,或者其其它線程所引用。
Java
堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC
堆」。從內存回收的角度來看,因爲收集器基本都採用分代收集算法,因此Java
堆中還能夠細分爲: 新生代和老年代,再細緻一點的有Eden
空間、From Survivor
空間、To Survivor
空間等。
根據Java
虛擬機規範的規定,Java
堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx
和-Xms
控制)。若是堆中沒有內存內存完成實例分配,並且堆沒法擴展將報OOM
錯誤(OutOfMemoryError
)。
方法區同堆同樣,是全部線程共享的內存區域,爲了區分堆,又被稱爲非堆。
方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量,如static
修飾的變量加載類的時候就被加載到方法區中。
運行時常量池(Runtime Constant Pool
)是方法區的一部分,class
文件除了有類的字段、接口、方法等描述信息以外,還有常量池(Constant Pool Table
)用於存放編譯期間生成的各類字面量和符號引用,這部份內容將在類加載後進人方法區的運行時常量池中存放。
在老版JDK
,方法區也被稱爲永久代。由於沒有強制要求方法區必須實現垃圾回收,HotSpot
虛擬機以永久代來實現方法區,從而JVM
的垃圾收集器能夠像管理堆區同樣管理這部分區域,從而不須要專門爲這部分設計垃圾回收機制。不過自從JDK7
以後,Hotspot
虛擬機便將運行時常量池從永久代移除了。JDK8
真正開始廢棄永久代,使用元空間(Metaspace
)替代。
直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域。可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError
異常的出現.
在JDK 1.4
中新加人了NIO
(New Input/Output
)類,引人了一種基於通道(Channel
與緩衝區(Buffer
) 的IO
方式,它可使用Native
函數庫直接分配堆外內存,而後經過一個存儲在Java
堆中的DirectByteBuffer
對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於它避免了在Java
堆和Native
堆中來回複製數據。
Java
虛擬機管理的內存分爲方法區、堆、虛擬機棧、本地方法棧、程序計數器棧五大區域。
程序計數器是一塊很小的內存空間,它是線程私有的,能夠認做爲當前線程的行號指示器。
Java
棧同計數器也爲線程私有,生命週期相同,就是平時說的棧,棧描述的是Java
方法執行的內存模型。
本地方法棧是與虛擬機棧發揮的做用十分類似,區別是虛擬機棧執行的是Java
方法(也就是字節碼),而本地方法棧則爲虛擬機使用到的native
方法服務.
堆是Java
虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制,幾乎全部的對象實例都在這裏分配內存。
直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域,但它的存在能在一些場景中顯著提升性能。
本文由博客一文多發平臺 OpenWrite 發佈!
更多內容請點擊個人博客 沐晨