1. 程式人生 > >深入理解java虛擬機器-Java記憶體區域與記憶體溢位異常

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

開發十年,就只剩下這套架構體系了! >>>   

深入理解java虛擬機器

Java記憶體區域與記憶體溢位異常

執行時資料區域

程式計數器

執行緒私有,記憶體小,是當前執行緒執行的位元組碼行號指示器,位元組碼直譯器通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,例如(分支、迴圈、跳轉、異常處理、執行緒恢復)。

 

Java虛擬機器棧

執行緒私有,生命週期與執行緒相同,每個java方法在執行時都會建立一個棧幀

,用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊,每個方法從呼叫到執行完成的過程都對應一個棧幀在虛擬機器棧中從入棧到出棧過程。

 

本地方法棧

為java虛擬機器使用的Native方法服務(C語言編寫的)

 

Java堆

記憶體大,所有執行緒共享,在虛擬機器啟動時建立,唯一目的是存物件例項,大多物件例項以及陣列在堆上分配記憶體,(還有棧上分配,標量替換)

Java堆分為新生代、老年代(Eden區,S0區,S1區)進一步劃分的目的都是為了更好地回收記憶體,或者更快地分配記憶體

 

方法區

執行緒共享,儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的程式碼

等資料,回收目標主要是對於常量池的解除安裝和對型別的解除安裝。

 

執行時常量池

是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有常量池,用於存放編譯期生成的各種字面量符號引用,以及翻譯出來的直接引用,這部分內容在類載入後進入方法區的執行時常量池中存放。

直接記憶體

NIO類,基於通道與緩衝區的I/O方式,使用Native函式庫直接分配堆外記憶體,然後通過一個儲存在java堆中的Buffer物件作為這塊記憶體的直接引用進行操作,提高效能(避免在java堆和Native堆中來回複製資料)

HotSpot虛擬機器物件探祕

物件的建立

普通物件的建立,不包括陣列和Class物件的建立過程?

  • 虛擬機器遇到一條new指令時,首先將去檢查這個指令的引數是否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過,如果沒有,那必須先執行相應的類載入過程。
  • 在類載入檢查通過後,虛擬機器將會為新生物件分配記憶體(大小在類載入完成後便可完全確定),分配記憶體有指標碰撞(記憶體規整,垃圾收集器採用的是壓縮整理演算法),空閒列表(記憶體不規整)
  • 物件在虛擬機器建立頻繁,需要考慮併發下執行緒安全問題,解決辦法有:①同步分配記憶體操作—虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性;②把記憶體分配的動作按照執行緒劃分在不同的空間進行,也就是為每個執行緒的java堆中預分配各自的一小塊記憶體
  • 接下來,虛擬機器要對物件進行設定,比如物件是哪個類的例項,類的元資料資訊,物件的雜湊碼,物件的GC分代年齡等資訊儲存在物件頭中。
  • 執行init方法,把物件初始化

 

物件的記憶體佈局

分為三塊區域:物件頭、例項資料、對齊填充

物件頭

  • 包括兩個部分,第一個部分用於儲存自身執行時的資料,如hashCode,GC分代年齡,鎖狀態,執行緒持有的鎖,偏向執行緒ID,偏向時間戳等;
  • 第二部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項,但查詢物件的元資料資訊並不需要經過物件本身,後面討論…

例項資料

是物件真正儲存的有效資訊,也是程式程式碼中所定義的各種型別的欄位內容,無論是在父類中定義的還是在子類中定義的欄位都會記錄下來。

對齊填充

佔位符作用,當物件例項資料部分沒有對齊時,就需要通過對齊填充來補全。

 

物件的訪問走位

目前主流的訪問物件方式:使用控制代碼直接指標

使用控制代碼好處:

java棧的本地變量表中的reference儲存的是穩定的控制代碼地址,在物件被移動(垃圾收集過程)只會改變控制代碼中的例項資料指標,而reference本身不需要修改。

使用直接指標好處:

         速度更快,節省一次指標定位的時間開銷(物件訪問頻繁,可以節省時間),對應HotSpot而言,它使用直接指標方式進行物件訪問。

 

實戰:OutOfMemoryError異常

Java堆溢位

產生原因

java堆用於儲存物件例項,只要不斷地建立物件,並且保證GC Roots到物件之間可達路徑來避免垃圾回收機制清除這些演算法,那麼在物件數量達到最大堆的容量限制後,就會產生記憶體溢位異常。

解決辦法

  • 先通過記憶體映像分析工具(EMA)對Dump出來的堆快照進行分析,重點是確認是記憶體洩漏(沒用的物件還活著,死不了)還是記憶體溢位(都是有用的物件);
  • 如果是記憶體洩漏:可通過工具檢視洩漏物件到GC  Roots的引用鏈定位洩漏程式碼位置;如果是記憶體溢位:需要檢查虛擬機器的堆引數(-Xmx與-Xms)與機器記憶體對比看是否可以調大,再從程式碼層次檢查是否存在某些物件生命週期過長,持有狀態時間過長的情況,嘗試減少程式執行期的記憶體消耗。

 

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

  • 如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常。
  • 如果虛擬機器在擴充套件時無法申請到足夠的記憶體空間,將丟擲OutOfMemoryError 異常。

 

解決辦法

    1. 使用-Xss 引數設定棧記憶體容量
    2. 程式碼層次,少使用大量本地變數

 

方法區和執行時常量池溢位

執行時常量池也是方法區的一部分,方法區用於存放Class的相關資訊,比如類名、訪問修飾符、常量池、欄位描述、方法描述等,溢溢位可以在執行時產生大量的類區填充方法區。

 

本機直接記憶體溢位

由DirectMemory導致的記憶體溢位,一個明顯的特徵是在Heap Dump檔案中不會看到明顯的異常,如果發現OOM之後Dump檔案很小,而程式中又直接或者間接使用了NIO,就可以考慮這