JVM筆記(1.1)Java內存區域和內存溢出異常

運行時數據區域

image

程序計數器

做用:

  • 能夠看做是當前線程所執行的字節碼的行號指示器,經過改變這個計數器的值來選取下一跳須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成
  • 由於Java虛擬機的多線程執行是經過線程輪流切換並分配處理器執行時間的方式實現的。爲了線程切換後能恢復到正確的執行位置,每條線程都須要一個獨立的程序計數器
  • 若執行的是Java方法,則計數器爲正在執行的虛擬機字節碼地址;若爲Native方法,則爲空

Java虛擬機棧

它是線程私有的,生命週期和線程相同。數組

用處

他描述的是Java方法執行的內存模型:每一個方法執行的時候都會建立一個棧幀來存儲局部變量表(存放編譯期可知的各類基本數據類型、對象引用)、操做數棧、指向運行時常量池引用、方法返回地址安全

  • 局部變量表:存儲方法中的局部變量(包括在方法中聲明的非靜態變量以及函數形參)。對於基本數據類型的變量,則直接存儲它的值,對於引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就能夠肯定其大小了,所以在程序執行期間局部變量表的大小是不會改變的。
  • 操做數棧:對錶達式求值
  • 指向運行時常量池的引用:指向類中的常量
  • 方法返回地址:返回以前調用的地址
這個區域有兩個異常:
  • 線程請求的棧深度大於虛擬機所容許的深度,拋出StackOverflow異常
  • 若虛擬機棧能夠動態拓展,並拓展時沒法申請到足夠的內存,拋出OutOfMemoryError異常

Java本地方法棧

和虛擬機棧做用相似,本地方法棧是虛擬機使用的native方法服務。(HotSpot將本地方法棧和Java棧合二爲一)多線程

Java堆

Java堆是被全部線程共享的一塊內存區域,JVM中只有一個堆。jsp

目的:

這塊區域的惟一目的就是存放對象實例函數

分類:

  • 內存回收角度:
    • 新生代
    • 老年代
  • 內存分配角度:
    • 多個線程私有的分配緩衝區

Java堆能夠處於物理上不連續的空間,可是再邏輯上要連續佈局

方法區:

線程共享的內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。(也叫non-heap,爲了和堆區分)性能

不須要連續的內存、可擴展、可選擇不實現垃圾收集。這部分的內存回收目標主要是針對常量池的回收和對類型的卸載線程

當方法去沒法知足內存分配需求時,拋出OutOfMemoryError指針

運行時常量池:

它是方法區的一部分cdn

  • Class文件常量池常量池用於存放編譯期生成的各類字面量和符號引用,這部分將在類加載後進入方法區的運行時常量池中存放(運行期間也可將新的常量放入運行時常量池中,如String的intern()方法)
  • 運行時常量池具備動態性,即運行期間能夠將新的常量放入池中

直接內存

直接內存並非虛擬機運行時數據區的一部分,也不是Java 虛擬機規範中農定義的內存區域。在JDK1.4 中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O 方式,它可使用native 函數庫直接分配堆外內存,而後通脫一個存儲在Java堆中的DirectByteBuffer 對象做爲這塊內存的引用進行操做。

  • 本機直接內存的分配不會受到Java 堆大小的限制,受到本機總內存大小限制
  • 配置虛擬機參數時,不要忽略直接內存 防止出現OutOfMemoryError異常
直接內存(堆外內存)與堆內存比較
  • 直接內存申請空間耗費更高的性能,當頻繁申請到必定量時尤其明顯
  • 直接內存IO讀寫的性能要優於普通的堆內存,在屢次讀寫操做的狀況下差別明顯

舉例

接下來看一個例子來了解內存各空間具體的分配

對象的建立

  1. 當遇到new指令,首先檢查指令的參數可否在常量池中定位到一個符號引用,檢查這個符號引用表明的類是否被加載、解析、初始化過,沒有,執行類加載。
  2. 類加載完成後,準備在堆中分配內存
    1. 若堆中內存是規整的(即用過的放一邊,沒用過的放一邊),有一個指針做爲分界點,分配內存就是將這個指針向空閒的堆移動和對象大小相等的距離
    2. 若堆中內存是不規整的,虛擬機就要維護一個表來記錄哪些空間是可用的
    3. 這裏還有一個問題,就是線程安全問題,例如在建立對象a時,指針還沒來得及修改,對象b用同一個指針來分配內存。這就形成了線程不安全,這裏有兩個解決方案:
      1. 對分配內存空間的動做進行同步——CAS失敗重試保證原子性
      2. 把內存分配動做按照線程劃分在不一樣的空間,即每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝
  3. 內存分配完成後,虛擬機將分配到的內存空間初始化爲零值
  4. 對對象進行一些設置,如這個對象是誰的類實例、如何找到類的元數據信息、對象的哈希碼、對象的GC分代年齡
  5. 執行new指令以後執行方法(構造方法)

對象的內存佈局

對象在內存中的佈局可分爲3塊區域:對象頭、實例數據、對齊填充

  • 對象頭:(分爲兩部分,若爲數組,則還有還有一塊數據記錄數組長度)
    • 第一部分存儲對象自身運行時數據(哈希碼、GC分代年齡、鎖狀態標誌、線程持有鎖、偏向線程id、偏向時間戳登)
    • 第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例
  • 實例數據:保存程序所定義的字段內容
  • 對齊填充:不是必然存在,佔位符的做用,由於對象大小必須爲8字節的整數倍

對象訪問定位

建立完對象後,咱們還要能經過引用來操做堆

訪問方式主要分兩種:
  • 句柄,堆中會劃分一塊內存做爲句柄池,引用存儲的就是對象的句柄地址,句柄包含了對象實例數據和類型數據的地址信息。對象移動時只改變句柄的實例數據指針。如圖
  • 直接指針,速度快。如圖

句柄

image

直接指針

image

內存溢出異常

除了程序計數器外,虛擬機內存其餘運行時區域都有可能發生OOM(OutOdMemoryError)

Java堆溢出

  • 內存泄漏:指程序在申請內存後,沒法釋放已申請的內存空間,一次內存泄露危害能夠忽略,但內存泄露堆積後果很嚴重,不管多少內存,早晚會被佔光。
  • 內存溢出:指程序在申請內存時,沒有足夠的內存空間供其使用

爲防止內存泄漏、溢出,有幾個方法:

  • Thinking in Java中提到可讓引用變量不用時設置爲null
  • 儘可能少用靜態變量
  • 避免建立大對象
  • 對虛擬機調節配置

虛擬機棧和本地方法溢出

  • 若線程請求的棧深度大於虛擬機所容許的最大深度:拋出StackOverflowError
  • 若線程拓展棧時沒法申請到足夠的內存空間:拋出OutOfMemoryError

StackOverflow和OOM的區別:
單個線程下,內存沒法分配時,拋出的爲StackOverflowError
多個線程下,爲每一個線程的棧分配的越大,越容易產生內存溢出異常

方法區和運行時常量池溢出

動態大量生成Class應用、大量jsp生成(jsp會先轉爲servlet編譯)、OSGi的應用時要注意類的回收情況

本機直接內存溢出

如查看到Heap Dump文件小,又使用了NIO,多半是本機直接內存溢出

相關文章
相關標籤/搜索