天天看點

Java記憶體區域管理

目錄

1 關于自動記憶體管理

  1. Java是由jvm來管理記憶體,包括自動配置設定以及自動回收,是以它不容易出現記憶體洩漏和記憶體溢出問題。
  2. C/C++,由程式員手動管理記憶體,手動完成:使用前申請記憶體,使用後釋放記憶體。

2 運作時資料區域

Java虛拟機在執行Java程式的過程中會把它所管理的記憶體劃分為若幹個不同的資料區域。這些區域有各自的用途,以及建立和銷毀的時間。

Java虛拟機所管理的記憶體 包括以下幾個運作時資料區域:

Java記憶體區域管理

2.1 程式計數器

  1. 程式計數器(Program Counter Register):存儲目前線程所執行的位元組碼指令的記憶體位址。位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。【通過PC來尋找下一條要執行的指令】。
  2. 程式計數器是線程私有的。cpu通過輪流配置設定時間片來執行線程,為了線程切換後能恢複到正确的執行位置,顯然每個線程都需要有一個獨立的程式計數器。
  3. 如果線程正在執行的是一個Java方法,PC記錄的是正在執行的位元組碼指令的位址;如果正在執行的是本地(Native)方法,這個計數器值則應為空。【todo 為什麼本地方法時為空】

2.2 虛拟機棧

  1. 虛拟機棧描述的是Java方法執行的線程記憶體模型:每個方法被執行的時候,jvm會同步建立一個棧幀[後續章節詳解](Stack Frame)用于存儲局部變量表、操作數棧、動态連接配接、方法出口等資訊。
  2. 虛拟機棧也是線程私有的,它的生命周期與線程相同。

2.2.1 局部變量表

JDK8之前,對jvm記憶體的認知隻停留在:堆記憶體(Heap)和棧記憶體(Stack),而“棧”通常就是指這裡講的虛拟機棧,或者更多的情況下隻是指虛拟機棧中局部變量表部分。      
  1. 局部變量表存儲:基本資料類型(八種:boolean、byte、char、short、int、 float、long、double)、對象引用(可能是一個指向對象起始 位址的引用指針)和returnAddress 類型(指向了一條位元組碼指令的位址)。
  2. 局部變量表所需的記憶體空間在編譯期間完成配置設定,當進入一個方法時,這個方法需要在棧幀中配置設定多大的局部變量空間是完全确定 的,在方法運作期間不會改變局部變量表的大小。【這裡的大小僅值變量槽的數量】
  3. 局部變量表以局部變量槽(Slot)來存儲資料,其中64位長度的long和 double類型的資料會占用兩個變量槽,其餘的資料類型隻占用一個。(通常一個槽占據N個位元組,N大小由不同的虛拟機實作決定)。

關于棧的記憶體資料分析,在後續章節中會有更深入分析,在本節中這裡僅引入概念。

2.2.2 操作數棧

關于方法調用時的進棧跟出棧的原理,在《深入了解計算機系統》系列筆記的後續章節中會進行總結。

2.3 本地方法棧

  1. 本地方法棧(Native Method Stacks)與虛拟機棧作用是非常相似的,其差別隻是虛拟機棧為虛拟機執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛拟機使用到的本地(Native) 方法服務。
  2. 線程私有的。

2.4 堆

  1. Java堆(Java Heap)存儲對象的執行個體。
  2. Java堆是線程共享的,且比較大的一塊記憶體區域,在虛拟機啟動時建立。
  3. Java堆既可以被實作成固定大小的,也可以是可擴充的,不過目前主流的Java虛拟機都是按照可擴充來實作的(通過參數-Xmx和-Xms設定)。
  4. 可彈性伸縮,但不會超過設定的最大容量,如果在Java堆中沒有記憶體完成執行個體配置設定,并且堆也無法再 擴充時,Java虛拟機将會抛出OutOfMemoryError異常
  5. Java堆可以處于實體上不連續的記憶體空間中,但在邏輯上它應該 被視為連續的。

2.5 方法區

  1. 方法區存儲已被虛拟機加載的class的類型資訊、常量、靜态變量、即時編譯器編譯後的代碼緩存等資料。
  2. 方法區是線程共享。
  3. 方法區不需要連續的記憶體、可以選擇固定大小或者可擴充,甚至還可以選擇不實作垃圾收集(因為這部分記憶體通常不滿足回收條件)
  4. 方法區無法滿足新的記憶體配置設定需求時,将抛出 OutOfMemoryError異常。

2.5.1 運作時常量池

  1. 存放編譯期生成的各種字面量與符号引用
  2. 運作時常量池(Runtime Constant Pool)是方法區的一部分

3 直接記憶體

  1. 首先,直接記憶體(Direct Memory)并不是虛拟機運作時資料區的一部分,也不是《Java虛拟機規範》中 定義的記憶體區域
  2. 它可能導緻OutOfMemoryError異常出現,有必要了解。
在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區 (Buffer)的I/O方式,它可以使用Native函數庫直接配置設定堆外記憶體,然後通過一個存儲在Java堆裡面的 DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了 在Java堆和Native堆中來回複制資料。

顯然,本機直接記憶體的配置設定不會受到Java堆大小的限制,但是,既然是記憶體,則肯定還是會受到 本機總記憶體(包括實體記憶體、SWAP分區或者分頁檔案)大小以及處理器尋址空間的限制,一般服務 器管理者配置虛拟機參數時,會根據實際記憶體去設定-Xmx等參數資訊,但經常忽略掉直接記憶體,使得 各個記憶體區域總和大于實體記憶體限制(包括實體的和作業系統級的限制),進而導緻動态擴充時出現 OutOfMemoryError異常。      

4 總結