1. 程式人生 > >深入理解java虛擬機器-第二章:java記憶體區域與記憶體洩露異常

深入理解java虛擬機器-第二章:java記憶體區域與記憶體洩露異常

2.1概述:

  java將記憶體的管理(主要是回收工作),交由jvm管理,確實很省事,但是一點jvm因記憶體出現問題,排查起來將會很困難,為了能夠成為獨當一面的大牛呢,自然要了解vm是怎麼去使用記憶體的。

2.2執行時的資料區域

  vm會將管理的記憶體劃分為不同的區域,不同的區域間有各自的用途,以及建立和銷燬時間。具體的區域劃分如下圖:

  注:執行引擎跟本地庫介面不是記憶體資料區,方法區跟堆記憶體才是共享的記憶體資料區

2.2.1程式計數器

  是一塊較小的記憶體地址,可以認為是當前執行緒所執行的位元組碼的行號指示器。在概念模式中(不同的虛擬機器可以選擇自己的實現方式),位元組碼直譯器工作時,通過改變這個計數器的值來選擇下一條執行的位元組碼命令。分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都是依賴這個計數器完成的。

  在JVM中多執行緒是通過執行緒輪流切換並分配處理器執行時間實現的,即在同一個時刻,一個處理器只會執行一個執行緒的命令,所以為了執行緒切換能夠回到正確的執行位置,每條執行緒都要有獨立的計數器。

  補充:

  如果執行緒執行的是java方法,那麼計數器記錄的是位元組碼指令的地址,如果是Native方法,計數器則為空(Undefined),該區域在jvm規範中也沒有OOM。

2.2.2java虛擬機器棧

  是執行緒私有的,生命週期與執行緒相同。

  虛擬機器棧描述的是方法執行的記憶體模型,方法在執行的時候會建立棧幀(Stack Frame),用於儲存區域性變量表,運算元棧、動態連結、方法出口等資訊。方法從呼叫到執行完成,對應一個棧幀從入棧到出棧的過程。

  區域性變量表中存放的是:編譯期可知的各種基本資料型別(八個基本資料型別),物件引用(可能是指向物件地址的引用指標,也可能是執行代表物件的控制代碼)和returnAddress型別(指向了一條位元組碼指令的地址)。

  long與double會佔據兩個區域性變數空間(slot),其他佔據一個,區域性變量表所需記憶體大小編譯期間就已經完成分配, 方法執行期間不會改變區域性變量表的大小。

  jvm規範中,對棧規定了兩個異常狀態,執行緒請求的棧深度大於虛擬機器所允許的深度會丟擲StackOverflowError異常。

  虛擬機器棧可以動態擴充套件來避免棧溢位,但是當擴充套件無法申請到足夠的記憶體時,就會丟擲OutofMemoryError異常。

2.2.3本地方法棧

  功能作用與虛擬機器棧是非常一致的,區別就在於:java虛擬機器棧為執行java方法服務,本地方法棧為虛擬機器使用的Native方法服務。虛擬機器規範並沒有對本地方法棧做硬性要求。

  HotSpot直接把本地方法棧跟虛擬機器棧合二為一,本地方法棧也會丟擲兩個異常。棧溢位與記憶體溢位。

2.2.4java堆

  java堆(java Heap)記憶體是vm管理的最大的記憶體。

  java堆被所有執行緒共享的記憶體區域。該記憶體區域存在的目的是存放物件例項。

  規範中:所有的物件例項以及陣列都是要求在堆上進行分配,但是隨著JIT編譯器的發展與逃逸分析技術,出現了棧上分配和標量替換,這會導致有一些微妙的變化。

  java堆是垃圾收集器的主要管理區域,也成為GC堆(Garbage Collected Heap),收集器都選擇分代收集演算法。

  java堆可以細分為:新生代和老年代:再細緻點有Eden空間、From Survivor空間、 To Survivor空間等。

  從記憶體分配的角度看,java堆內是可以劃分出多個執行緒私有的分配緩衝區的(Thread Local Allocation Buffer TLAB)。

  劃分的詳細的目的在於方便更好地回收記憶體。

  補充:java堆可以不處於物理上的連續記憶體,只要邏輯上連續就可以了,當堆無法繼續擴充套件時,也會丟擲OutOfMemoryError。

2.2.5方法區:

  方法區(Method Area)跟java堆一樣,是執行緒共享的記憶體區域,用於儲存VM載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等,還有一個別名(Non-Heap)非堆

  對於HotSpot來說,方法區也可稱為永久代(Permanent Generation),本質上兩者並不等價,僅僅是因為HotSpot虛擬機器把分帶收集器擴充套件到了方法區(用永久代來實現方法區)。

  用永久代實現方法區會容易導致記憶體溢位問題(永久代有-XX:MaxPermSize的上限)。在jdk1.7中,已經把原來放在永久代的字串常量池移出永久代了。

  VM規範對於方法區來說,也可以不選擇連續的實體記憶體,還可以選擇固定大小或者可擴充套件,甚至你還可以選擇不實現垃圾回收。

  針對方法區的回收主要是針對常量池的回收和對型別的解除安裝,當方法區無法滿足記憶體分配的時候,就會出現OutOfMemoryError異常。

2.2.6執行時常量池

  執行時常量池(Runtime Constant Pool)是方法區的一部分。

  class檔案除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中。

  VM規範沒有對執行時常量池的細節規範,一般情況下除了class的符號引用外,還會把直接引用也存在執行時常量池。

  執行時常量池相對於Class檔案的常量池另一個重要特徵是動態性,執行期間也可以放入新的常量進入常量池。比較多的用法是String類的intern()。

  作為方法區的一部分,當然也會OutofMemoryError異常

2.2.7直接記憶體

  直接記憶體(Direct Memory)並不是VM執行時資料區的一部分,也不是VM規範中定義的記憶體區域,但是如果該區域被頻繁使用,也會導致OutOfMemoryError異常。

  NIO,引入了基於通道(Channel)與緩衝區(Buffer)的I/O方式,可以直接使用Native函式分配堆外記憶體,然後通過在堆中的DirectByteBuffer物件作為該記憶體的引用進行操作。

  雖然該記憶體不受堆的限制,但是也可能受實體記憶體的限制,所以也可能因為設定的引數問題,導致動態擴充套件時出現OutOfMemoryError異常。

簡單總結:講了各個記憶體區域的一些實現細節跟部分VM規範,除了程式計數器外其他所有的記憶體區域都存在記憶體溢位異常,甚至於非jvm的記憶體區域直接記憶體,也有可能出現記憶體溢位異常。

2.3HotSpot虛擬機器的物件探祕

 2.3.1物件的建立

  建立物件是通過關鍵字new來建立的。

  虛擬機器收到new命令時,會去常量池中檢查是否有對應引數的類的符號引用,並檢查這個符號引用是否已經被載入、解析和初始化過,如果沒有的話,那麼要先進行類載入。

  類載入的檢查完成後,就要對新生物件進行記憶體分配了,分配方式有兩種,根據堆記憶體是否規整可以分為兩類:指標碰撞(Bump the Pointer)、空閒列表(Free List)

  指標碰撞:堆記憶體規整,分配記憶體的過程僅僅是將指標向空閒空間挪動一段與物件大小一致的距離。

  空閒列表:如果記憶體不規整,那麼已使用的記憶體與空閒記憶體互動,虛擬機器會維護一個記錄表,記錄記憶體是否可用,在分配時從列表中找足夠記憶體劃分給例項,更新記錄表。

  堆是否規整又跟垃圾收集器有關,使用Serial、ParNew等帶Compact過程的收集器時,採用指標碰撞;使用CMS這種基於Mark-Sweep演算法的收集器時,採用空閒列表的方式。

  除了分配記憶體外,還需要考慮在併發下的安全問題,虛擬機器採用了CAS配上失敗重試的方式保證更新操作的原子性;另一種方式是把記憶體分配的動作按照執行緒劃分在不同的空間中,即每個執行緒在java堆中預先分配一個記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB),哪個執行緒需要分配記憶體,就在哪個執行緒的TLAB分配,只有TLAB用完需要進行新增時,才進行同步鎖定操作。虛擬機器是否使用TLAB,可以通過配置:-XX:+/-UseTLAB引數來設定。

  記憶體分配完成,VM還需要將分配的記憶體空間都初始化為零值(物件頭除外),如果使用TLAB的話,那麼該過程也會提前至TLAB時進行,這一步操作保證了物件例項欄位在java程式碼中可以不賦初始值就直接使用,程式可以直接訪問到這些欄位資料型別的對應值。

  完成初始化工作後,VM要設定物件的物件頭,相關資訊:物件是哪個類的例項,如何找到類的元資料資訊,物件的雜湊值,物件的GC分帶年齡等資訊。

  完成以上步驟後,VM的視角,新的物件已經產生了。但是java程式碼角度,物件建立才剛開始,<init>方法沒有執行,所有欄位都還是0,執行完new指令後執行<init>方法後才算物件建立完畢。

  簡單描述一下VM視角與程式視角下物件的建立流程:

    VM  ->  類是否初始化  記憶體分配  記憶體空間初始化  物件頭賦值

    java程式  ->  類是否初始化  記憶體分配  記憶體空間初始化  物件頭賦值  <init>方法

2.3.2物件的記憶體佈局

  物件在記憶體中的佈局分為三個區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)

  HotSpot的物件頭包含兩部分資訊,第一部分:儲存物件自身的執行時資料、第二部分型別指標。

  儲存執行時資料有:雜湊嗎,GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等,根據虛擬機器位數不同分別為32/64bit,稱為Mark Word,實際上是一個可動態的資料結構,以便以小空間儲存更多的資訊。

  型別指標:即物件指向它的類元資料的指標,VM通過指標確定物件屬於哪個類。

  注:如果物件是一個數組,那麼物件頭中還必須有一塊用於記錄陣列長度的資料。

  例項資料:物件真正儲存的有效資訊。

  父類定義在前,子類災後,儲存的順序還受VM分配策略引數(FieldsAllocationStyle)和java原始碼中定義順序影響。

  HotSpot的順序是:從長到短,且欄位相同的放在一起。

  第三部分對齊填充不是必然存在的,僅僅是佔位符的作用。由於HotSpot的VM自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,那麼物件就必須是8位元組的整數倍了,因為物件頭部分是8位元組的倍數,所有當例項資料沒有對齊時,對齊填充就用來補齊。

2.3.3物件的訪問定位

  虛擬機器規範只規定了一個指向物件的引用,但是沒有規定具體的方式。所以還是根據虛擬機器的具體實現來表述對物件的訪問。常規的是兩種控制代碼式與直接指標式:

  如果是採取:控制代碼訪問的話,那麼java堆會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了例項資料地址跟資料型別地址

  如果採用直接指標訪問,那麼java堆物件的佈局就必須考慮如何放置訪問型別資料的相關資訊,reference中儲存的是物件地址。

   

兩種物件的訪問方式各有優劣,控制代碼的好處時,reference中儲存的是穩定的控制代碼地址,物件被移動時,只改變控制代碼中的例項資料指標,而reference不改變。

  直接指標訪問最大的好處是速度快,節省了一次指標定位的時間開銷。

   HotSpot採用的就是直接指標訪問的方式。

2.4實戰OOM異常

   本節的目的:1、通過程式碼驗證java虛擬機器規範中描述的各個執行時區域儲存的內容。

        2、幫助判斷實際工作中是什麼問題導致哪些區域記憶體溢位,什麼原因導致該區域記憶體溢位,出現問題該怎麼辦。

  使用如下jvm引數:

-verbose:gc -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

2.4.1java堆溢位

  堆中存放物件例項,只要不停創造物件,且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制清楚物件(將在垃圾回收機制處講),那麼物件達到閾值後自然會產生記憶體溢位。

  使用引數:-XX:+HeapDumpOnOutOfMemoryError可讓虛擬機器在出現記憶體溢位時Dump當前的記憶體堆轉儲快照,便於事後分析。

示例程式碼:

public class HeapOOM {
    static class OOMObject {}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }

    }
}

  解決這個區域的異常,一般通過記憶體映像分析工具,對Dump出來的堆轉儲快照進行分析,重點是確認記憶體中的物件是否是必要的,也就是確認是記憶體洩漏(Memory Leak)還是記憶體溢位(Memory Overflow)。

  注:上面這句也簡潔直觀地表達了記憶體洩漏與記憶體溢位的區別。

  記憶體洩漏:可以通過工具看洩漏物件到GC Roots的引用鏈,可以找到洩漏物件時通過怎樣的路徑與GC Roots相關聯並導致垃圾回收器無法自動回收它們。只要掌握了洩漏物件的型別資訊及GC Roots引用連的資訊,就可以準確地定位出洩漏程式碼的位置。

  記憶體溢位:如果物件都必須存活,那麼虛擬機器的堆引數(-Xmx與-Xms)與機器實體記憶體對比看是否能夠調大;從程式碼角度看,是否有物件的生命週期過長、持有狀態時間過長,以期減少程式執行期間記憶體的消耗。

2.4.2虛擬機器棧以及本地方法棧溢位

  HotSpot中不區分虛擬機器棧與本地方法棧,所有-Xoss實際無效,只設置-xss即可。

  對於虛擬機器棧和本地方法棧來說,會出現兩種異常:

    執行緒請求的棧深度大於虛擬機器所允許的最大深度,即丟擲:StackOverflowError。

    虛擬機器擴充套件棧時,無法申請到足夠的記憶體空間,即丟擲:OutOfMemoryError

  其實,當棧空間無法繼續分配時,到底是記憶體太小,還是已用棧空間太大,本質都是同一件事情的兩種描述。

  註明:在單執行緒條件下,無論是棧幀過大還是虛擬機器容量太小,都會丟擲異常StackOverflowError。

  在多執行緒條件下,通過不斷建立執行緒的方式是會產生記憶體溢位的,但是產生記憶體溢位與棧空間是否足夠大無關,在這種情況下,為每個執行緒分配的記憶體越大,越容易棧溢位(總量一定,單次消耗越大,越容易滿)。

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

  String.intern()是一個Native方法,它的作用是:如果字串常量池中已經包含了一個等價次string物件的字串,返回常量池中的該物件,否則將string物件包含的字串新增到常量池中,返回此string物件的引用。

相關推薦

深入理解java虛擬機器-第二java記憶體區域記憶體洩露異常

2.1概述:   java將記憶體的管理(主要是回收工作),交由jvm管理,確實很省事,但是一點jvm因記憶體出現問題,排查起來將會很困難,為了能夠成為獨當一面的大牛呢,自然要了解vm是怎麼去使用記憶體的。 2.2執行時的資料區域   vm會將管理的記憶體劃分為不同的區域,不同的區域間有各自的用途,以及建立和

深入理解java虛擬機器第二讀書筆記

目錄    常量池:    直接記憶體 一. java虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域。 1.程式計數器: 1.它可以看作是當前

深入Java虛擬機器】之一Java記憶體區域記憶體溢位

記憶體區域     Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各資料區所儲存的資料型

深入理解Java虛擬機器讀書筆記2----垃圾收集器記憶體分配策略

二 垃圾收集器與記憶體分配策略 1 JVM中哪些記憶體需要回收?     JVM垃圾回收主要關注的是Java堆和方法區這兩個區域;而程式計數器、虛擬機器棧、本地方法棧這3個區域隨執行緒而生,隨執行緒而滅,隨著方法結束或者執行緒結束記憶體自然

深入理解Java虛擬機器》讀書筆記第二Java記憶體區域記憶體溢位異常

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域:方法區、虛擬機器棧、本地方法棧、堆、程式計數器 程式計數器(ProgramCounterRegister):一塊較小的記憶體空間,看作當前執行緒所執行的位元組碼的行號指示器;位元組碼

學習筆記深入理解Java虛擬第二:Java內存區域內存溢出異常(1)

block 物理 裝載 成熟 memory from ram 權力 一個 學習筆記:深入理解Java虛擬機 第二章:Java內存區域與內存溢出異常(1) Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的"高墻",墻外面的人想進去,墻裏面的

學習筆記深入理解Java虛擬第二:Java內存區域內存溢出異常(2)

保留 頻繁 深入 一是 init方法 對象的引用 整理 緩沖 出現 學習筆記:深入理解Java虛擬機 第二章:Java內存區域與內存溢出異常(2) 三、HotSpot虛擬機對象探秘 1.對象的創建 ? 在Java程序運行過程中時刻都有對象被創建。在語言層面上,創建對象(例如

深入理解Java虛擬機器—JVM高階特性實踐 周志明 著》之第2 Java記憶體區域記憶體溢位異常

1、Java虛擬機器所管理的記憶體包括以下幾個執行時資料區域: 2、程式計數器:          1. 可以看作是當前執行緒所執行的位元組碼的行號指示器,是一塊較小的記憶體空間;  &nbs

深入理解JVM虛擬機器】第1 走進java

1.1Java技術體系 Sun 官方定義的Java技術體系包括以下幾個組成部分: Java 程式設計語言 各種硬體平臺上的 Java 虛擬機器 Class 檔案格式 Java API 類庫 第三方 Java 類庫 1.1.1 Java API 按照技術所服

深入Java虛擬機器 第二版》之第1 Java體系結構介紹

1、Java技術的核心就是“Java虛擬機器”。 2、Java體系結構四個獨立的技術: ·Java程式設計語言; ·Java class檔案格式; ·Java應用程式設計介面(API); ·Java虛擬機器; 3、編寫並執行一個Java程式,對四種技術的體驗:

深入理解JVM—第二Java記憶體區域記憶體溢位異常

1,概述 Java較C、C++,Java可以利用虛擬機器的自動記憶體管理機制,避免繁瑣的記憶體分配與回收。不容易出現記憶體洩漏和記憶體溢位問題。 記憶體洩漏:指程式申請到的記憶體空間不再歸還(無法歸還),可使用完該記憶體空間的程式也不能再訪問該空間(

深入理解JVM虛擬機器4Java class介紹解析實踐

深入理解JVM虛擬機器4:Java class介紹與解析實踐 轉自https://juejin.im/post/589834a20ce4630056097a56 前言 身為一個java程式設計師,怎麼能不瞭解JVM呢,倘若想學習JVM,那就又必須要了解Class檔案,Class之

深入理解java虛擬機器系列(一)java記憶體區域記憶體溢位異常

文章主要是閱讀《深入理解java虛擬機器:JVM高階特性與最佳實踐》第二章:Java記憶體區域與記憶體溢位異常 的一些筆記以及概括。 好了開始。如果有什麼錯誤或者遺漏,歡迎指出。 一、概述 先上一張圖 這張圖主要列出了Java虛擬機器管理的記憶體的幾個區域。 常有人

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

第2章 Java記憶體區域與記憶體溢位異常 2.3 HotSpot 虛擬機器物件探祕 2.3.1 物件的建立 Java作為一門面向物件語言,在程式執行過程中會產生大量的物件,在語言層面上來看,建立一個物件僅僅需要一個new關鍵字即可,但是在虛

深入理解JAVA虛擬機器讀書筆記HotSpot虛擬機器物件探祕

HotSpot虛擬機器物件探祕 1、物件的建立 虛擬機器遇到一條new指令時,首先將去檢查這個指令的引數是否能在常量池中定位這個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化過。如果沒有,那必須先執行響應的類載入過程。(類載入會在後面的章節給出)。

深入理解 Java 虛擬機器》讀書筆記Java 記憶體區域記憶體溢位異常

前言 最近開始看這本書,記得前段時間拿起這本書的時候,心情是相當沉重的!當時的劇本是這樣的—— 內景。家裡 - 下午 我(畫外):唉,有點無聊啊!(偶然撇過書架)這麼多書得看到什麼時候啊,要不要拿一本翻翻呢?但是在家裡好像有點看不下去啊,是太安逸了嗎?最近那本《圖解 HTTP》也還沒看完,感覺暫時有點不想

java虛擬機器(第二版) 第二總結 (三)-手工復現java虛擬機器記憶體溢位(OutOfMemoryError異常)

  文章概述 的java虛擬機器記憶體溢位的簡要概述,復現堆記憶體,棧記憶體,方法區的執行時常量池記憶體等區域的溢位情況,以及上述區域發生記憶體溢位的判斷方式和解決思路。   1,概述:      IDE為eclipse,需要在執

java虛擬機器(第二版) 第二總結 (二)

本文總結下java虛擬機器的第二章的第三小節   1、(原文2.3)HotSpot虛擬機器物件探祕   文中以HotSpot虛擬機器為例,講述了java物件的建立以及訪問。   當我們寫的new關鍵字被虛擬機器執行時:  <

java虛擬機器(第二版) 第二總結 (一)

本文主要是up主閱讀java虛擬機器(第二版) 第二章總結的總結,因為第一章主要講了java體系的過去和展望,故就暫時總結了,感興趣的可以閱讀原書。 另外推薦看原書,原書對我總結的知識有更細緻的解讀。 眾所周知,java虛擬機器對記憶體的自動管理,讓java程式設計師少了好多手動管理記憶

學習筆記1:深入理解Java虛擬機器——JVM高階特性最佳實踐_走進java_java記憶體區域記憶體溢位異常

第一部分:走進java Java虛擬機器 程式碼在華章下載 jdk釋出了六個命令列工具和兩個視覺化故障處理工具。 推薦書籍 設計原本 領域特定語言 現在著名的Java虛擬機器 hotspot vm(預設) jrockit vm j9 vm jdk sun jdk op