Java的內存結構

Java 的運行時數據區域分爲五大數據區域。這些區域各有各的用途,建立及銷燬時間。以下圖所示,其中方法區和堆是全部線程共享的,棧,本地方法棧和程序虛擬機則爲線程私有的。
image.png
根據Java虛擬機規範,Java虛擬機管理的內存分爲方法區、堆、虛擬機棧、本地方法棧、程序計數器棧五大區域。算法

程序計數器(Program Counter Register)

程序計數器是一塊很小的內存空間,它是線程私有的,能夠認做爲當前線程的行號指示器。segmentfault

在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。因爲Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,對於一個處理器(若是是多核CPU那就是一核)來講,在一個肯定的時刻只會執行一條線程中的指令,而一條線程中有多個指令,爲了線程切換能夠恢復到正確執行位置,每一個線程都需有獨立的一個程序計數器,不一樣線程之間的程序計數器互不影響,獨立存儲。數組

若是線程執行的是個Java方法,那麼計數器記錄的是虛擬機字節碼指令的地址。若是爲native底層方法,那麼計數器爲空(Undefined)。這塊內存區域是虛擬機規範中惟一沒有OutOfMemoryError的區域。數據結構

Java棧(虛擬機棧)(Java Virtual Mechine Stacks)

Java棧同計數器也爲線程私有,生命週期相同,就是平時說的棧,棧描述的是Java方法執行的內存模型
每一個方法被執行的時候都會建立一個棧幀用於存儲局部變量表,操做棧,動態連接,方法出口等信息。每個方法被調用的過程就對應一個棧幀在虛擬機棧中從入棧到出棧的過程。多線程

棧幀( Stack Frame): 是用來存儲數據和部分過程結果的數據結構。
棧幀的位置: 內存 -> 運行時數據區 -> 某個線程對應的虛擬機棧 -> here
棧幀大小肯定時間: 編譯期肯定,不受運行期數據影響。

一般有人將Java內存區分爲棧(Heap)和堆(Heap),實際上Java內存比這複雜,這麼區分多是由於咱們最關注的與對象內存分配關係最密切的是這兩個,平時說的棧通常就是指局部變量表。
image.png
局部變量表: 一片連續的內存空間,用來存放方法參數,以及方法內定義的局部變量,存放着編譯期間已知的數據類型(八大基本類型和對象引用(reference類型),returnAddress類型。它的最小的局部變量表空間單位爲Slot,虛擬機沒有指明Slot的大小,但在JVM中,longdouble類型數據明確規定爲64位,這兩個類型佔2Slot,其它基本類型固定佔用1Slot函數

reference類型: 與基本類型不一樣的是它不等同自己,即便是String,內部也是char數組組成,它多是指向一個對象起始位置指針,也可能指向一個表明對象的句柄或其餘與該對象有關的位置。性能

returnAddress類型: 指向一條字節碼指令的地址大數據

須要注意的是,局部變量表所須要的內存空間在編譯期完成分配,當進入一個方法時,這個方法在棧中須要分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表大小優化

Java虛擬機棧可能出現兩種類型的異常:spa

  • 線程請求的棧深度大於虛擬機容許的棧深度,將拋出StackOverflowError
  • 虛擬機棧空間能夠動態擴展,當動態擴展是沒法申請到足夠的空間時,拋出OutOfMemory異常。

本地方法棧(Native Method Stack)

本地方法棧是與虛擬機棧發揮的做用十分類似,區別是虛擬機棧執行的是Java方法(也就是字節碼),而本地方法棧則爲虛擬機使用到的native方法服務,可能底層會調用C或者C++,咱們打開jdk安裝目錄能夠看到也有不少用C編寫的文件,可能就是native方法所調用的C代碼。

堆(Java Heap)

堆是Java虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一做用就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。

Java虛擬機規範中的描述是: 全部的對象實例以及數組都要在堆上分配,可是隨着JIT編譯器的發展與逃逸分析技術逐斷成熟,棧上分配、標量替換優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。

即時編譯器: 能夠把 Java的字節碼,包括須要被解釋的指令的程序轉換成能夠直接發送給處理器的指令的程序)

逃逸分析: 經過逃逸分析來決定某些實例或者變量是否要在堆中進行分配,若是開啓了逃逸分析,便可將這些變量直接在棧上進行分配,而非堆上進行分配。這些變量的指針能夠被全局所引用,或者其其它線程所引用。

Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC堆」。從內存回收的角度來看,因爲收集器基本都採用分代收集算法,因此Java堆中還能夠細分爲: 新生代和老年代,再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。

根據Java虛擬機規範的規定,Java 堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx-Xms控制)。若是堆中沒有內存內存完成實例分配,並且堆沒法擴展將報OOM錯誤(OutOfMemoryError)。

方法區(Method Area)

方法區同堆同樣,是全部線程共享的內存區域,爲了區分堆,又被稱爲非堆。

方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量,如static修飾的變量加載類的時候就被加載到方法區中。

運行時常量池( Runtime Constant Pool)是方法區的一部分, class文件除了有類的字段、接口、方法等描述信息以外,還有常量池( Constant Pool Table)用於存放編譯期間生成的各類字面量和符號引用,這部份內容將在類加載後進人方法區的運行時常量池中存放。

在老版JDK,方法區也被稱爲永久代。由於沒有強制要求方法區必須實現垃圾回收,HotSpot虛擬機以永久代來實現方法區,從而JVM的垃圾收集器能夠像管理堆區同樣管理這部分區域,從而不須要專門爲這部分設計垃圾回收機制。不過自從JDK7以後,Hotspot虛擬機便將運行時常量池從永久代移除了。JDK8真正開始廢棄永久代,使用元空間(Metaspace)替代。

直接內存(Direct Memory)

直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域。可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError異常的出現.

JDK 1.4中新加人了NIO (New Input/Output)類,引人了一種基於通道(Channel 與緩衝區(Buffer) 的IO方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於它避免了在Java堆和Native堆中來回複製數據。

總結

Java虛擬機管理的內存分爲方法區、堆、虛擬機棧、本地方法棧、程序計數器棧五大區域。

程序計數器是一塊很小的內存空間,它是線程私有的,能夠認做爲當前線程的行號指示器。

Java棧同計數器也爲線程私有,生命週期相同,就是平時說的棧,棧描述的是Java方法執行的內存模型

本地方法棧是與虛擬機棧發揮的做用十分類似,區別是虛擬機棧執行的是Java方法(也就是字節碼),而本地方法棧則爲虛擬機使用到的native方法服務.

堆是Java虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制,幾乎全部的對象實例都在這裏分配內存。

直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域,但它的存在能在一些場景中顯著提升性能。

本文由博客一文多發平臺 OpenWrite 發佈!
更多內容請點擊個人博客 沐晨
相關文章
相關標籤/搜索