1. 程式人生 > >第2章 Java記憶體區域與記憶體溢位異常

第2章 Java記憶體區域與記憶體溢位異常

2.2 執行時資料區

方法區、堆、執行引擎、本地庫介面
虛擬機器棧、本地方法棧、程式計數器

加粗是所有執行緒共享的資料區,其他是執行緒隔離的資料區

2.2.1 程式計數器

是較小的記憶體空間,是當前執行緒執行的位元組碼的行號指示器。位元組碼指示器就是通過改變這個計數器的值來選下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等都需要這個計算器來完成。

多個執行緒來回切換於處理器上,為保證執行緒切換後恢復到正確執行位置,每個執行緒都有一個獨立的程式計數器

如果執行緒正執行的是一個Java方法,計數器記錄的是正執行的虛擬機器位元組碼指令的地址;如果是native方法,計數器值為空。

此區域是唯一一個虛擬機器中沒規定OutOfMemoryError情況的區域。

2.2.2 虛擬機器棧

虛擬機器棧是執行緒私有的,它生命週期和執行緒相同。是Java方法執行的記憶體模型。
方法在執行同時會建立棧幀用於儲存區域性變量表、運算元棧、動態連結、方法出口資訊。
方法從呼叫到執行完全,代表棧幀就是在虛擬機器棧中入棧到出棧。

虛擬機器棧也就是虛擬機器棧中區域性變量表部分。
區域性變量表存放各種基本資料型別、物件引用和returnAddress型別(指向一條位元組碼指令的地址)。
64位長度的long和double型別資料會佔用2個區域性變數空間slot,其餘資料型別只佔1個。區域性變量表記憶體空間在編譯器完成分配,方法執行期間不會改變區域性變量表的大小。

有兩種異常情況:執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;虛擬機器棧可動態擴充套件,如果擴充套件時無法申請到足夠記憶體,就丟擲OutOfMemoryErrory異常。

2.2.3 本地方法棧

和虛擬機器棧作用是相似的,本地方法棧則為虛擬機器使用的native方法服務。本地方法也會丟擲出StackOverflowError和OutOfMemoryErrory異常。

2.2.4 Java堆

堆是虛擬機器管理的記憶體最大的一塊,堆是所有執行緒共享的一塊記憶體區域,在虛擬機器啟動建立。唯一目的就是存放物件例項。
所有的物件例項以及陣列都要在堆上分配。

Java堆是GC管理的主要區域,收集器都採用分代收集演算法,所以Java堆細分為:
新生代和老年代;再細一點有:Eden空間、From Survivor空間、To Survivor空間。

無論如何劃分,都與存放內容無關,各空間儲存的都是物件例項。
堆可以處於不連續的空間中,只要邏輯連續。堆是可以擴充套件的,通過-Xmx和-Xms控制。
如果堆中沒有記憶體完成例項分配,堆也無法擴充套件,就丟擲OutOfMemoryErrory異常。

2.2.5 方法區

和堆一樣,是各個執行緒共享的記憶體區域,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
方法區也稱為永久代,HotSpot選擇把GC擴充套件至方法區,並不是所有虛擬機器都是方法區就是永久代的。
現在會將放棄永久代逐步用Native Memory實現方法區,把永久代的字串常量池移出。

方法區和堆一樣不需要連續的記憶體和可以選擇固定大小和可擴充套件。
在這個區域的GC行為比較少出現,但是也會有GC,當方法區無法滿足記憶體分配需求,就丟擲OutOfMemoryErrory異常。

2.2.6 執行時常量池

是方法區的一部分。Class檔案中有一項資訊是常量池,用於存放編譯器生成的各種字面量和符號引用,這部分在類載入後進入方法區的執行時常量池存放。
執行時常量池具備動態性,執行期間也可以將新的常量放入池中,例如String類的intern方法。

2.2.7 直接記憶體

不是執行時資料區的一部分,但該記憶體被頻繁使用,也可能導致OutOfMemoryErrory異常。
這個記憶體和NIO類有關,直接記憶體不受堆大小的限制。但是還會受到本機實體記憶體大小的限制。當進行引數配置時,有些時候忽略了直接記憶體,使各個記憶體區域總和大於實體記憶體限制,從而導致動態擴充套件時出現OutOfMemoryErrory異常。

2.3 HotSpot虛擬機器物件

講解Hotspot虛擬機器和記憶體區域的Java堆,堆中物件分配、佈局和訪問全過程。

2.3.1 物件建立

當虛擬機器遇到new指令,先去檢查指令的引數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已被載入、解析和初始化。如果沒有,就先執行類載入。

類載入後,虛擬機器為新生物件分配記憶體。物件的記憶體大小在類載入後便可確定,物件分配空間的任務相當於把一個確定大小的記憶體從Java堆中分出來。

虛擬機器維護一個列表,記錄那些記憶體塊是可用的,分配給物件的時候會更新記錄。

物件建立是虛擬機器中非常頻繁的行為,修改指標指向記憶體的位置,在併發下也不是執行緒安全的。這有兩種解決方法:第一個是對分配記憶體空間的動作進行同步處理,虛擬機器採用CAS加失敗重試保證更新操作的原子性;
第二種是將記憶體分配按執行緒劃分在不同的空間中。
每個執行緒在Java堆先分配一小塊記憶體,稱為本地執行緒分配緩衝,各個執行緒在自己的TLAB上分配,TLAB用完並分配新的TLAB時,才同步鎖定。

之後就是虛擬機器對物件進行設定,該物件是哪個類的例項、找到類的元資料資訊、物件雜湊碼、物件GC分代年齡等。

對虛擬機器來說新物件已經產生,但從程式看物件剛才開始建立,此時init方法還沒有執行。

2.3.2 物件記憶體佈局

物件在記憶體中儲存的佈局分為3塊區域:物件頭、例項資料、對齊填充。

物件頭包含兩部分:
第一個儲存物件自身執行時資料,如雜湊碼、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳。

物件頭另一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過該指標確定這個物件是哪個類的例項。

如果物件是陣列,物件頭還必須有用於記錄陣列長度的資料,虛擬機器可以用過元資料資訊確定物件的大小,但是陣列不行。

例項資料部分是物件真正儲存的有效資訊,也是程式中定義的各種型別的欄位內容。無論是父類繼承還是子類定義的。

對齊填充不是必然存在也沒什麼含義。起著佔位符的作用。物件起始地址必須是8位元組的整數倍,物件例項資料需要通過該方式補全。

2.3.3 物件的訪問定位

程式通過棧上的reference資料操作堆上的具體物件。
目前主流訪問物件的方式有兩種:控制代碼和直接指標。
1. 使用控制代碼,堆會劃分出一塊記憶體作為控制代碼池,reference儲存的就是物件的控制代碼地址,控制代碼中包含物件例項資料和型別資料各自的具體地址資訊。
2. 直接指標訪問,堆物件的佈局中必須考慮如果放置訪問型別資料的相關資訊,reference儲存的直接就是物件地址。

這裡寫圖片描述

這裡寫圖片描述

這兩種各有優勢:
控制代碼好處就是reference儲存穩定的控制代碼地址,物件被移動時只會改變控制代碼中的例項資料指標,reference本身不會改變。

直接指標最大好處就是速度更快,節省一次指定定位的時間開銷。Hotspot就是用直接指標。

2.4 OutOfMemoryError異常

2.4.1 Java堆溢位

堆用於儲存物件例項,只要不斷建立物件,保證GC到物件間有可達路徑避免回收,數量到達最大堆容量限制就產生記憶體溢位。

堆大小為20M,不可擴充套件(堆最小值-Xms引數和最大值-Xmx引數設定一樣可避免自動擴充套件)
引數:-XX:+HeapDumpOnOutOfMemoryError可讓虛擬機器出現記憶體溢位異常時Dump出堆轉儲快照以便分析

/**
 * VM:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();

        while(true){
            list.add(new OOMObject());
        }
    }
}

堆得OOM異常時常見的記憶體溢位異常。
要解決這個區域的異常,首先用記憶體映像分析工具對dump出來的堆轉儲快照進行分析,確認記憶體中物件是否是必要的,判斷到底是記憶體洩漏還是記憶體溢位。

如果是記憶體洩漏,通過工具進一步檢視洩漏物件到GC Roots的引用鏈。掌握洩漏物件的資訊和引用鏈資訊,可以定位出洩漏程式碼位置。

不是洩漏,檢查虛擬機器對引數-Xmx和-Xms,看是否還可以調大,檢查某些物件是否生命週期過長、持有狀態時間過長,減少程式執行期的記憶體消耗

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

Hotspot不區分虛擬機器棧和本地方法棧,-Xoss設定本地方法棧大小,是無效的

棧只由-Xss引數設定

關於虛擬機器棧和本地方法棧有兩種異常:
1. 如果執行緒請求棧深度大於虛擬機器允許的最大深度,丟擲StackOverflowError異常
2. 如果虛擬機器擴充套件棧時申請不到足夠記憶體空間,丟擲OutOfMemoryError異常

-Xss引數減少棧記憶體容量。

/**
 * VM:-Xss128k
 */
 public class StackSOF {
     private int stackLength = 1;

     public void stackLeak(){
         stackLength++;
         stackLeak();
     }

     public static void main(String[] args) throws Throwable {
        StackSOF oom = new StackSOF();
        try{
            oom.stackLeak();
        }catch(Throwable e){
            System.out.println(oom.stackLength);
            throw e;
        }
     }
 }

單執行緒下,無論是棧幀太大還是棧容量太小,都丟擲StackOverflowError異常。

多執行緒下,每個執行緒分配記憶體越大,越容易產生記憶體溢位異常。

相對於記憶體溢位,出現StackOverflowError異常有錯誤棧可以閱讀,較容易找出問題所在。預設情況下棧深度在1000-2000沒問題,滿足正常方法呼叫。
但如果多執行緒導致記憶體溢位,只能通過減少最大堆和減小棧容量換取更多執行緒。

建立執行緒導致記憶體溢位

/**
 * VM:-Xss2m
 */
public class StackOOM {

     private void dontStop(){
         while(true){
         }
     }

     public void stackLeakByThread(){
         while(true){
             Thread thread = new Thread(new Runnable(){
                 @Override
                 public void run(){
                     dontStop
                 }
             });
             thread.start();
         }
     }

     public static void main(String[] args) throws Throwable {
        StackOOM oom = new StackOOM();
        oom.stackLeakByThread();
     }
 }

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

執行時常量池是方法區的一部分,JDK1.7開始在逐步去除永久代。

String.intern()是native方法。
作用:如果字串常量池中包含一個等於此String物件的字串,則返回代表池中這個字串的String物件;否則,將此String物件包含的字串放到常量池中,返回此String物件的引用。

方法區用於存放Class相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等。這些區域的測試,基本思路是執行時產生大量的類填滿方法區。
藉助CGLib直接操作位元組碼執行時生成大量動態類。

很多主流框架都會運用CGLib技術,因此很容易遇到這樣的記憶體溢位異常。

/**
 * VM:-XX:PermSize=10m -XX:MaxPermSize=10m
 */
 public class MethodAreaOOM {

     public static void main(String[] args){
         while(true){
             Enhancer enhancer = new Enhancer();
             enhancer.setSuperclass(OOMObject.class);
             enhancer.setUseCache(false);
             enhancer.setCallback(new MethodInterceptor(){
                 public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) throws Throwable {
                     return proxy.invokeSuper(obj,args);
                 }
             });
             enhancer.create();
         }
     }

     static class OOMObject{
     }
 }

2.4.4 本機直接記憶體溢位

可通過-XX:MaxDirectMemorySize指定直接記憶體,不指定預設和堆最大值一樣

使用unsafe分配本機記憶體

/**
 * VM:-Xmx20m -XX:MaxDirectMemorySize=10m
 */
 public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);
        while(true){
            unsafe.allocateMemory(_1MB);
        }
    }
 }

直接記憶體導致的溢位,堆dump檔案不會看出明顯異常,如果發現OOM之後dump檔案很小,程式又直接或間接呼叫了NIO,可以考慮一下。

相關推薦

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

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

JVM 2 Java記憶體區域記憶體溢位異常

可以參考下,這個寫的簡練 https://blog.csdn.net/seu_calvin/article/details/51404589 1 概述 對於java程式設計師來說,在虛擬機器自動記憶體管理機制的幫助下,不需要為每一個new操作去寫配對的delete/free程式碼,不

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

2.2 執行時資料區 方法區、堆、執行引擎、本地庫介面 虛擬機器棧、本地方法棧、程式計數器 加粗是所有執行緒共享的資料區,其他是執行緒隔離的資料區 2.2.1 程式計數器 是較小的記憶體空間,是當前執行緒執行的位元組碼的行號指示器。位元組碼指示器就

2-Java記憶體區域記憶體溢位異常

Java記憶體區域與記憶體溢位異常 概述 對於Java程式設計師來說,在虛擬機器自動記憶體管理機制的幫助下,不再需要為每一個new操作去寫配對的delete/free程式碼,不容易出現記憶體洩漏和記憶體溢位問題,由虛擬機器管理記憶體。 執行時資料區域 Java虛擬機器在執行

筆記:深入理解JVM 2 Java記憶體區域記憶體溢位

1、JVM 執行時資料區 所有執行緒共享的資料區:方法區(持久代)、堆區 執行緒隔離的資料區:程式計數器、Java虛擬機器棧區 堆區構成:新生代 ( 由Eden, From Survivor, To Survivor 構成)、老生代 執行時常量池:方法區一部分,用於存放編

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

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

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

執行時資料區域 程式計數器 程式計數器是一塊較小的記憶體空間,他可以看作是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會通過 一些更搞笑的方式去實現),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要

JAVA虛擬機器(JVM)劃重點 第二 Java記憶體區域記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件 Java物件的建立 1、類載入過程

JAVA虛擬機器(JVM)劃重點 第二 Java記憶體區域記憶體溢位異常 之 執行時資料區域

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。 JVM劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 執行時資料區域 概述 執行時資料區域 程式計數器 Java虛擬機

第二Java記憶體區域記憶體溢位異常

2.1 概述       對於Java程式設計師來說,虛擬機器具有自動記憶體管理機制,不容易出現記憶體洩漏和記憶體溢位問題。也正是因為Java程式設計師把記憶體控制的機制交給了Java虛擬機器,一旦出現記憶體洩漏和溢位方面的問題,如果不瞭解虛擬機器是怎樣使用記憶體的,那排查

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

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

第二Java記憶體區域記憶體溢位異常

JVM記憶體模型及特徵: 堆: 1.JVM管理記憶體中最大的一塊,被所有執行緒共享 2.唯一目的是存放物件例項和陣列 3.垃圾蒐集器管理的主要區域 4.可擴充套件,通過-Xmx和-Xms來控制 5.如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutO

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

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

第二 java記憶體區域記憶體溢位異常

一、執行時的資料區域 java虛擬機器執行時的資料區域 主要分為以下幾個區域: 1 、程式計數器     它佔用較小的一塊記憶體空間,可以看做是當前執行緒執行的位元組碼的行號指示器。每個執行緒都需要擁有自己的的程式計數器,以滿足在多執行緒情況下,執行緒不斷切換自身能夠

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

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

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

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

java虛擬機器—-java記憶體區域記憶體溢位異常

一,java虛擬機器所管理的執行時資料區域分為:程式計數器、java虛擬機器棧、本地方法棧、java堆、方法區、執行時常量池。                     1,程式計數器:

jvm_:垃圾收集記憶體分配策略

1:回收哪些記憶體: 程式計數器,虛擬機器棧,本地方法棧屬於執行緒私有,隨執行緒而生,隨執行緒而滅,所以主要考慮方法區和堆記憶體的回收:2 2:哪些物件可以被回收:   引用計數演算法:   可達性分析演算法:GCRoots包括:                   虛擬機器棧中的引用的物件;  

《深入理解java虛擬機器》讀書筆記(二)---- Java記憶體區域記憶體溢位異常

執行時資料區域 java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域: 1、程式計數器 程式計數器是一塊較小的記憶體空間,它可以看作是當前執行緒所執行位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼直譯器的工作就是通過改變這個計數器的值來選取下一條需要執

JVM-Java記憶體區域記憶體溢位

JVM虛擬機器執行時資料區結構分為:      其中方法區和堆是所有執行緒共享的記憶體區域,而Java棧、本地方法棧、程式計數器是執行緒私有的。 我們詳細介紹執行時資料區的各個區域及其作用。  程式計數器:   一塊較小的記憶體空間,位元組碼指示器工作時通過改變計數器的值來選取下一條需