深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常

Tongson發表於2018-03-14

概述

Java程式設計師把記憶體控制的權力交給了Java虛擬機器,一旦出現記憶體洩露和溢位方面的問題, 如果不瞭解虛擬機器是怎樣使用記憶體的,那排查錯誤將會成為一項異常艱難的工作。 接下來就從概念上介紹Java虛擬機器記憶體的各個區域,講解這些區域的作用、服務物件以及其中可能產生的問題。

也是瞭解Java虛擬器的記憶體管理。

執行時資料區域

深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常

程式計數器(Program Counter Register)

我的理解就是ID。給當前執行緒一個ID,通過改變這個ID來選取下一條需要執行的位元組碼指令。

“執行緒私有”:每條執行緒都需要一個獨立的程式計數器。

一個處理器(一個核心)在一個確定的時刻只會執行一條執行緒中的指令。

Java虛擬機器棧(Java Virtual Machine Stacks)

生命週期與執行緒共存

用於儲存區域性變數表、操作棧、動態連結、方法出口等資訊。

幀棧(Stack Frame)

每個方法被執行的時候都會建立一個幀棧。

方法被呼叫直至執行完成的過程=從入棧到出棧過程。

區域性變數表

存放編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別,它不等於物件本身,根據不同虛擬機器實現,它可能是一個指向物件起始地址的指標,也可能指向一個代表物件的控制程式碼或者其他與此物件相關的位置) 和 returnAddress型別(指向了一條位元組碼指令的地址)

所需的記憶體空間在編譯期間完成分配。

異常

  • StackOverflowError:當前執行緒請求的棧深度大於虛擬機器所允許的深度時,會丟擲這個異常。
  • OutOfMemoryError:當虛擬機器棧可以動態擴充套件時(當前大部分虛擬機器都可以),如果無法申請足夠多的記憶體就會丟擲 OutOfMemoryError。

本地方法棧(Native Method Stacs)

本地方法棧與虛擬機器棧所發揮的作用很相似,他們的區別在於虛擬機器棧為執行 Java 程式碼方法服務,而本地方法棧是為 Native 方法服務。

異常

  • StackOverflowError
  • OutOfMemoryError

Java堆(Java Heap)

記憶體最大。

是被所有執行緒共享的一塊記憶體區域。

在虛擬機器啟動時建立。

用於存放物件例項。

是垃圾收集器管理的主要區域("GC堆")。

異常

當堆無法再擴充套件時,會丟擲 OutOfMemoryError 異常。

方法區(Method Area)

是被所有執行緒共享的一塊記憶體區域。(與Java堆一樣)

方法區存放的是類資訊、常量、靜態變數、即時編譯期編譯後的程式碼等資料。

回收

由於使用反射機制的原因,虛擬機器很難推測那個類資訊不再使用,因此這塊區域的回收很難。垃圾收集的行為在這個區域是比較少出現的。

另外,對這塊區域主要是針對常量池回收,值得注意的是 JDK1.7 已經把常量池轉移到堆裡面了

執行時常量池(Runtime Constant Pool)

是方法區的一部分。 存放Class檔案中常量池。 用於存放編譯期生成的各種字面量和符號引用,在類載入後存放到方法區的執行時常量中。

異常

受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常。

直接記憶體(Direct Memory)

虛擬機器執行時資料區和Java虛擬機器規範中定義的記憶體區域以外的記憶體。

這部分記憶體也被頻繁地使用。

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以適用Native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆裡面的DirectByteBuffer物件作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高效能,因為避免了在Java和Native堆中來回複製資料。

異常

當各個記憶體區域的總和大於實體記憶體限制(包括物理上的和作業系統的限制),從而導致動態擴充套件時出現OutOfMemoryError異常。

物件訪問

Object obj=new Object();

Object obj-

將會放映到Java棧的本地變數表中,作為一個reference型別資料出現。

new Object()-

將會反映到Java堆中,形成一塊儲存了Object型別所有例項資料值的結構化記憶體,根據具體型別以及虛擬機器實現的物件記憶體佈局的不同,這塊記憶體的長度是不固定的。

Java堆中

還必須包含能查詢到此物件型別資料(如物件型別、父類、實現的介面、方法等)的地址資訊。

方法區中

則儲存這些型別資料。(物件型別、父類、實現的介面、方法等)

reference型別

在Java虛擬機器範圍裡面只規定了一個指向物件的引用,並沒有定義這個引用應該通過哪種方式定位,以及訪問到Java堆中的物件的具體位置,因此不同虛擬機器實現的物件訪問方式會有所不同

主流訪問方式有兩種

  • 使用控制程式碼訪問方式-- Java堆中劃出一塊記憶體來作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料和型別資料各自的具體地址資訊。

深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常
優點:reference中儲存的是穩定的控制程式碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改控制程式碼中的例項資料指標,而reference本身不需要被修改。

  • 使用直接指標訪問方式-- Java堆物件的佈局中就必須考慮如何防止訪問型別資料相關資訊,reference中直接儲存的就是物件地址。

深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常
優點:速度更快,節省了一次指標定位的時間開銷,由於物件的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。。

OutOfMemoryError異常

Java堆溢位

物件數量到達最大堆的容量限制後產生記憶體溢位異常。 java.lang.OutOfMemoryError Java heap space

一般需要藉助記憶體映像分析工具來解決。 記憶體洩露(Memory Leak)該回收的沒被回收 記憶體溢位(Memory Overflow)空間不夠

虛擬機器棧和本地方法棧溢位

StackOverflowError:執行緒請求的棧深度大於虛擬機器所允許的最大深度。 OutOfMemoryError:虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間

執行時常量池溢位

常量池分配在方法區內。 OutOfMemoryError PermGen Space

方法區溢位

用於存放Class的相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等。 方法區溢位也是一種常見的記憶體溢位異常,一個類如果要被垃圾收集器回收掉,判斷條件非常苛刻的。在經常動態生成大量Class的應用中,需要特別注意類的回收狀況。

本機直接記憶體溢位

並沒有真正向作業系統申請分配記憶體,而是通過計算得知記憶體無法分配。

相關文章