天天看點

Java 運作時記憶體區域

本文參考《深入了解java虛拟機》

java運作時記憶體區域如圖所示:

Java 運作時記憶體區域

1.程式計數器

用于訓示下一條執行哪個指令

由于java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間的方式實作的,在任意一個确定的時刻,處理器隻會執行一條線程中的指令,是以為了能夠正确切換,每個線程都有自己的程式計數器

2.java虛拟機棧

與程式計數器一樣,它也是線程私有的,它的生命周期與線程相同。每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛拟機棧中入棧到出棧的過程。

它用于存儲:局部變量表,操作數棧,動态連結,方法出口等資訊

局部變量表存放了編譯期可知的各種基本資料類型(int,double等),對象引用(reference類型,不等同于對象本身,可能是一個指向對象起始位址的引用指針)

在java虛拟機規範中,對該區域規定了兩種異常:

若線程請求的棧深度大于虛拟機允許的深度,将抛出StackOverflowError異常

若虛拟機棧可以動态擴充(目前大部分的Java虛拟機都可動态擴充,隻不過Java虛拟機規範中也允許固定長度的虛拟機棧),若擴充時無法申請到足夠的記憶體,就會抛出OutOfMemoryError異常

3.本地方法棧

它與虛拟機棧發揮的作用非常相似,它們之間的差別不過是虛拟機棧為虛拟機執行java方法服務,本地方法棧則為虛拟機使用到的native方法服務。在虛拟機規範中對本地方法棧中方法使用的語言,使用方式與資料結構并沒有強制規定,是以具體的虛拟機可以自由實作它。甚至有的虛拟機(比如hotspot)直接就把本地方法棧和虛拟機棧合二為一。與虛拟機棧一樣,本地方法棧區域也會抛出StackOverflowError和OutOfMemoryError異常 線程私有

4.Java堆

對大多數應用來說,Java堆是虛拟機管理記憶體中最大的一塊,是被所有線程共享的一塊區域,在虛拟機啟動時建立。此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在此處配置設定記憶體。這一點在Java虛拟機規範中的描述是:所有的對象執行個體以及數組都要在這裡配置設定記憶體,但是随着jit編譯器的發展與逃逸分析技術逐漸成熟,棧上配置設定、标量替換優化技術将會導緻一些微妙的變化發生,所有的對象都配置設定在堆上也漸漸變得不是那麼絕對了。

由于Java堆上是垃圾收集器管理的主要區域,是以很多時候也被稱作“GC堆”,由于現在收集器基本都采用分代收集算法,是以java堆中還可細分為:新生代、老年代,再細緻一點的還有Eden空間,From Survivor空間,To Survivor空間等。

根據Java虛拟機規範的規定,Java堆可以處于實體上不連續的記憶體空間中,隻要邏輯上是連續的即可,就像我們的磁盤空間一樣,在實作時既可以設定成固定大小,也可擴充,不過當錢主流的虛拟機都是按照可擴充來實作的(通過-Xmx和-Xms控制)。若在堆中沒有足夠記憶體完成執行個體配置設定,且堆也無法再擴充時,将抛出OutOfMemoryError異常

5.方法區

方法區和Java堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。雖然Java虛拟機規範把方法區描述為堆的一個邏輯部分,但是它有一個别名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

對于習慣再HotSpot虛拟機上開發的開發者來說,很多人把方法區稱為“永久代”,本質上兩者并不等價,僅僅是因為hotspot虛拟機的設計團隊選擇把GC分代收集的機制擴充至方法區,或者說用永久代的方式來實作方法區而已,這樣hotspot的垃圾收集器可以像管理Java堆一樣管理這部分記憶體,能夠省去專門為方法區編寫記憶體管理代碼的工作。現在看來這并不是一個好主意,因為這樣更容易遇到記憶體溢出的問題(永久代有-XX:MaxPermSize的上限),且有極少數方法(例如String.intern())會因為這個原因在不同的虛拟機下有不同的表現,是以對于hotspot虛拟機,根據官方釋出的路線圖資訊,現在也有放棄永久代并逐漸改為采用Native Memory來實作方法區的規劃了,并且在JDK1.7的hotspot中,已經把原本放在永久代的字元串從常量池中移出。

6.運作常量池

是方法區的一部分,Class檔案中除了有類的版本、字段、方法、接口等描述資訊外,還有一項是常量池,用于存放編譯期生成的各種字面量和符号引用,這部分内容将在類加載後進入方法區的運作時常量池中存放。

7.直接記憶體

并不是虛拟機運作時資料區的一部分,也不是Java虛拟機規範中定義的記憶體區域。

在jdk1.4中加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫直接配置設定堆外記憶體(我的了解就是讀檔案的緩沖區),然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回複制資料。

顯然,直接記憶體就是本機直接配置設定的記憶體,它并不會受到Java堆大小的限制,但是它肯定會受到本機總記憶體大小的限制。伺服器管理者在配置虛拟機參數時,會根據實際記憶體設定-Xmx參數資訊,但經常忽略直接記憶體,使各個記憶體區域的總和大于實體記憶體的限制,進而會導緻OutOfMemoryError異常。