欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

學(xué)習(xí)JVM之java內(nèi)存區(qū)域與異常

 更新時(shí)間:2016年07月21日 17:05:26   投稿:daisy  
關(guān)于JVM內(nèi)存區(qū)域的知識(shí)對(duì)于初學(xué)者來說其實(shí)是很重要的,了解Java內(nèi)存分配的原理,這對(duì)于以后JAVA的學(xué)習(xí)會(huì)有更深刻的理解。下面來看看詳細(xì)介紹。

一、前言

java是一門跨硬件平臺(tái)的面向?qū)ο蟾呒?jí)編程語言,java程序運(yùn)行在java虛擬機(jī)上(JVM),由JVM管理內(nèi)存,這點(diǎn)是和C++最大區(qū)別;雖然內(nèi)存有JVM管理,但是我們也必須要理解JVM是如何管理內(nèi)存的;JVM不是只有一種,當(dāng)前存在的虛擬機(jī)可能達(dá)幾十款,但是一個(gè)符合規(guī)范的虛擬機(jī)設(shè)計(jì)是必須遵循《java 虛擬機(jī)規(guī)范》的,本文是基于HotSpot虛擬機(jī)描述,對(duì)于和其它虛擬機(jī)有區(qū)別會(huì)提到;本文主要描述JVM中內(nèi)存是如何分布、java程序的對(duì)象是如何存儲(chǔ)訪問、各個(gè)內(nèi)存區(qū)域可能出現(xiàn)的異常。

二、JVM中內(nèi)存分布(區(qū)域)

JVM在執(zhí)行java程序的時(shí)會(huì)把內(nèi)存分為多個(gè)不同的數(shù)據(jù)區(qū)域進(jìn)行管理,這些區(qū)域有著不一樣的作用、創(chuàng)建和銷毀時(shí)間,有的區(qū)域是在JVM進(jìn)程啟動(dòng)時(shí)分配,有的區(qū)域則與用戶線程(程序本身的線程)的生命周期相關(guān);按照J(rèn)VM規(guī)范,JVM管理的內(nèi)存區(qū)域分為以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域:


1、虛擬機(jī)棧

這塊內(nèi)存區(qū)域是線程私有的,隨線程啟動(dòng)而創(chuàng)建、線程銷毀而銷毀;虛擬機(jī)棧描述的java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行開始會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame),用于存儲(chǔ)局部變量表、操作數(shù)棧,動(dòng)態(tài)鏈接、方法出口等。每個(gè)方法的調(diào)用執(zhí)行和返回結(jié)束,都對(duì)應(yīng)有一個(gè)棧幀在虛擬機(jī)棧入棧和出棧的過程。

局部變量表顧名思義是存儲(chǔ)局部變量的內(nèi)存區(qū)域:存放編譯器期可知的基本數(shù)據(jù)類型(8種java基本數(shù)據(jù)類型)、引用類型、返回地址;其中占64位的long和double類型數(shù)據(jù)會(huì)占用2個(gè)局部變量空間,其它數(shù)據(jù)類型只占用1個(gè);由于類型大小確定、變量數(shù)量編譯期可知,所以局部變量表在創(chuàng)建時(shí)是已知大小,這部分內(nèi)存空間能在編譯期完成分配,并且在方法運(yùn)行期間不需要修改局部變量表大小。

在虛擬機(jī)規(guī)范中,對(duì)這塊內(nèi)存區(qū)域規(guī)定了兩種異常:

1.如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度(?),將拋出StackOverflowError異常;

2.如果虛擬機(jī)可以動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展是無法申請(qǐng)到足夠內(nèi)存,將拋出OutOfMemory異常;

2、本地方法棧

本地方法棧同樣也是線程私有,而且和虛擬機(jī)棧作用幾乎是一樣的:虛擬機(jī)棧是為java方法執(zhí)行提供出入棧服務(wù),而本地方法棧則是為虛擬機(jī)執(zhí)行Native方法提供服務(wù)。

在虛擬機(jī)規(guī)范中,對(duì)本地方法棧實(shí)現(xiàn)方式?jīng)]有強(qiáng)制規(guī)定,可以由具體虛擬機(jī)自由實(shí)現(xiàn);HotSpot虛擬機(jī)是直接把虛擬機(jī)棧和本地方法棧合二為一實(shí)現(xiàn);對(duì)于其他虛擬機(jī)實(shí)現(xiàn)這一塊的方法,讀者有興趣可以自行查詢相關(guān)資料;

與虛擬機(jī)棧一樣,本地方法棧同樣會(huì)拋出StackOverflowError和OutOfMemory異常。

3、程序計(jì)算器

程序器計(jì)算器也是線程私有的內(nèi)存區(qū)域,可以認(rèn)為是線程執(zhí)行字節(jié)碼的行號(hào)指示器(指向一條指令),java執(zhí)行時(shí)通過改變計(jì)數(shù)器的值來獲的下一條需要執(zhí)行的指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等執(zhí)行順序都要依賴這個(gè)計(jì)數(shù)器來完成。虛擬機(jī)的多線程是通過輪流切換并分配處理器執(zhí)行時(shí)間實(shí)現(xiàn),處理器(對(duì)多核處理器來說是一個(gè)內(nèi)核)在一個(gè)時(shí)刻只能在執(zhí)行一條命令,因此線程執(zhí)行切換后需要恢復(fù)到正確的執(zhí)行位置,每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)算器。

在執(zhí)行一個(gè)java方法時(shí),這個(gè)程序計(jì)算器記錄(指向)當(dāng)前線程正在執(zhí)行的字節(jié)碼指令地址,如果正在執(zhí)行的是Native方法,這個(gè)計(jì)算器的值為undefined,這是因?yàn)镠otSpot虛擬機(jī)線程模型是原生線程模型,即每個(gè)java線程直接映射OS(操作系統(tǒng))的線程,執(zhí)行Native方法時(shí),由OS直接執(zhí)行,虛擬機(jī)的這個(gè)計(jì)數(shù)器的值是無用的;由于這個(gè)計(jì)算器是一塊占用空間很小的內(nèi)存區(qū)域,為線程私有,不需要擴(kuò)展,是虛擬機(jī)規(guī)范中唯一一個(gè)沒有規(guī)定任何OutOfMemoryError異常的區(qū)域。

4、堆內(nèi)存(Heap)

java 堆是線程共享的內(nèi)存區(qū)域,可以說是虛擬機(jī)管理的內(nèi)存最大的一塊區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建;java堆內(nèi)存主要是存儲(chǔ)對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例(包括數(shù)組)都是存儲(chǔ)在這里,因此這也是垃圾回收(GC)最主要的內(nèi)存區(qū)域,有關(guān)GC的內(nèi)容這里不做描述;

按照虛擬機(jī)規(guī)范,java堆內(nèi)存可以處于不連續(xù)的物理內(nèi)存中,只要邏輯上是連續(xù)的,并且空間擴(kuò)展也沒有限制,既可以是固定大小,也可以是棵擴(kuò)展的;如果堆內(nèi)存沒有足夠的空間完成實(shí)例分配,而且也無法擴(kuò)展,將會(huì)拋出OutOfMemoryError異常。

5、方法區(qū)

方法區(qū)和堆內(nèi)存一樣,是線程共享的內(nèi)存區(qū)域;存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯期編譯后的代碼等數(shù)據(jù);虛擬機(jī)規(guī)范對(duì)于方法區(qū)的實(shí)現(xiàn)沒有過多限制,和堆內(nèi)存一樣不需要連續(xù)的物理內(nèi)存空間,大小可以固定或者可擴(kuò)展,還可以選擇不實(shí)現(xiàn)垃圾回收;當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí)將會(huì)拋出OutOfMemoryError異常。

6、直接內(nèi)存

直接內(nèi)存并不是虛擬機(jī)管理內(nèi)存的一部分,但是這部分內(nèi)存還是可能被頻繁用到;在java程序使用到Native方法時(shí)(如 NIO,有關(guān)NIO這里不做描述),可能會(huì)直接在堆外分配內(nèi)存,但是內(nèi)存總空間大小是有限的,也會(huì)遇到內(nèi)存不足的情況,一樣會(huì)拋出OutOfMemoryError異常。

二、實(shí)例對(duì)象存儲(chǔ)訪問

上面第一點(diǎn)對(duì)虛擬機(jī)各區(qū)域內(nèi)存有個(gè)總體的描述,對(duì)于每個(gè)區(qū)域,都存在數(shù)據(jù)是如何創(chuàng)建、布局、訪問的問題,我們以最常使用的的堆內(nèi)存為例基于HotSpot說下這三個(gè)方面。

1、實(shí)例對(duì)象創(chuàng)建

當(dāng)虛擬機(jī)執(zhí)行到一條new指令時(shí),首先首先從常量池定位這個(gè)創(chuàng)建對(duì)象的類符號(hào)引用、判斷檢查類是否已經(jīng)加載初始化,如果沒有加載,則執(zhí)行類加載初始化過程(關(guān)于類加載,這里不做描述),如果這個(gè)類找不到,則拋出常見的ClassNotFoundException異常;

通過類加載檢查后,就是實(shí)際為對(duì)象分配物理內(nèi)存(堆內(nèi)存),對(duì)象所需的內(nèi)存空間大小是由對(duì)應(yīng)的類確定的,類加載后,這個(gè)類的對(duì)象所需的內(nèi)存空間是固定的;為對(duì)象分配內(nèi)存空間,相當(dāng)于要從堆中劃分出一塊出來分配給這個(gè)對(duì)象;

根據(jù)內(nèi)存空間是否連續(xù)(已分配和未分配是區(qū)分為完整的兩部分)分為兩種分配內(nèi)存方式:

1. 連續(xù)的內(nèi)存:已分配和未分配中間使用一個(gè)指針作為分界點(diǎn),對(duì)象內(nèi)存分配只需要指針向未分配內(nèi)存段移動(dòng)一段空間大小即可;這種方式稱 為“指針碰撞”。

2. 非連續(xù)內(nèi)存:虛擬機(jī)需要維護(hù)(記錄)一個(gè)列表,記錄堆中那些內(nèi)存塊的沒有分配的,在分配對(duì)象內(nèi)存時(shí)從中選擇一塊適合大小的內(nèi)存區(qū)域 分配給對(duì)象,并更新這個(gè)列表;這種方式稱為“空閑列表”。

對(duì)象內(nèi)存的分配也會(huì)遇到并發(fā)的問題,虛擬機(jī)使用兩種方案解決這個(gè)線程安全問題:第一使用CAS(Compare and set)+識(shí)別重試,保證分配操作的原子性;第二是內(nèi)存分配按照線程劃分不同的空間,即每個(gè)線程在堆中預(yù)先分配好一塊線程私有的內(nèi)存,稱為本地線程分配緩存區(qū)(Thread Local Allocation Buffer,TLAB);那個(gè)線程要分配內(nèi)存時(shí),直接從TLAB中分配出來,只有當(dāng)線程的TLAB分配完需要重新分配,才需要同步操作從堆中分配,這個(gè)方案有效的減少線程間對(duì)象分配堆內(nèi)存的并發(fā)情況出現(xiàn);虛擬機(jī)是否使用TLAB這種方案,是通過JVM參數(shù) -XX:+/-UseTLAB 設(shè)定。

完成內(nèi)存分配后,除對(duì)象頭信息外,虛擬機(jī)會(huì)將分配到的內(nèi)存空間初始化為零值,保證對(duì)象實(shí)例的字段可以不賦值就可直接使用到數(shù)據(jù)類型對(duì)應(yīng)的零值;緊接著,執(zhí)行 init 方法按照代碼完成初始化,才完成一個(gè)實(shí)例對(duì)象的創(chuàng)建;

2、對(duì)象在內(nèi)存的布局

在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存分為3個(gè)部分:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)、對(duì)齊填充(Padding):

其中對(duì)象頭又分兩個(gè)部分:一部分存儲(chǔ)對(duì)象運(yùn)行時(shí)數(shù)據(jù),包括哈希碼、垃圾回收分代年齡、對(duì)象鎖狀態(tài)、線程持有的鎖、偏向線程ID、偏向 時(shí)間戳等;在32位和64位虛擬機(jī)中,這部分?jǐn)?shù)據(jù)分別占用32位和64位;由于運(yùn)行時(shí)數(shù)據(jù)較多,32位或者64位不足以完全存儲(chǔ)全部數(shù)據(jù),所以 這部分設(shè)計(jì)為非固定格式存儲(chǔ)運(yùn)行時(shí)數(shù)據(jù),而是根據(jù)對(duì)象的狀態(tài)不同而使用不同位來存儲(chǔ)數(shù)據(jù);另一部分存儲(chǔ)對(duì)象類型指針,指向這個(gè)對(duì)象的 類,但這并不是必須的,對(duì)象的類元數(shù)據(jù)不一定要使用這部分存儲(chǔ)來確定(下面會(huì)講到);

實(shí)例數(shù)據(jù)則是存儲(chǔ)對(duì)象定義的各種類型數(shù)據(jù)的內(nèi)容,而這些程序定義的數(shù)據(jù)并不是完全按照定義的順序存儲(chǔ)的,它們是按照虛擬機(jī)分配策略和定義的順序確定:long/double、int、short/char、byte/boolean、oop(Ordinary Object Ponint),可以看出,策略是按照類型占位多少分配的,相同的類型會(huì)在一起分配內(nèi)存;而且,在滿足這些前提條件下,父類變量順序先于子類;

而對(duì)象填充這部分不是一定會(huì)存在,它僅僅是起到占位對(duì)齊的作用,在HotSpot虛擬機(jī)內(nèi)存管理是按照8字節(jié)為單位管理,因此當(dāng)分配完內(nèi)存后,對(duì)象大小不是8的倍數(shù),則由對(duì)齊填充補(bǔ)全;

3、對(duì)象的訪問
在java程序中,我們創(chuàng)建了一個(gè)對(duì)象,實(shí)際上我們得到一個(gè)引用類型變量,通過這個(gè)變量來實(shí)際操作一個(gè)在堆內(nèi)存中的實(shí)例;在虛擬機(jī)規(guī)范中,只規(guī)定了引用(reference)類型是指向?qū)ο蟮囊?,沒有規(guī)定這個(gè)引用是如何去定位、訪問到堆中實(shí)例的;目前主流的虛擬機(jī)中,主要有兩種方式實(shí)現(xiàn)對(duì)象的訪問:

1. 句柄方式:堆內(nèi)存中劃分出一塊區(qū)域作為句柄池,引用變量中存儲(chǔ)的是對(duì)象的句柄地址,而句柄中存儲(chǔ)了示例對(duì)象和對(duì)象類型的具體地址信息,因此對(duì)象頭中可以不包含對(duì)象的類型:



2. 指針直接訪問:引用類型直接存儲(chǔ)的是實(shí)例對(duì)象在堆中的地址信息,但是這就必須要求實(shí)例對(duì)象的布局中,對(duì)象頭必須包含對(duì)象的類型:


這兩種訪問方式各有優(yōu)勢(shì):當(dāng)對(duì)象地址改變(內(nèi)存整理、垃圾回收),句柄方式訪問對(duì)象,引用變量不需要改變,只需要改變句柄中的對(duì)象地址值就可;而使用指針直接訪問方式,則需要修改這個(gè)對(duì)象全部的引用;但是指針方式,可以減少一次尋址操作,在大量對(duì)象訪問的情況下,這種方式的優(yōu)勢(shì)比較明顯;HotSpot虛擬機(jī)就是使用這中指針直接訪問方式。

三、運(yùn)行時(shí)內(nèi)存異常

java程序內(nèi)存在運(yùn)行時(shí)主要可能發(fā)生兩種異常情況:OutOfMemoryError、StackOverflowError;那個(gè)內(nèi)存區(qū)域會(huì)發(fā)生什么異常,前面已經(jīng)簡單提到,除了程序計(jì)數(shù)器已外,其他內(nèi)存區(qū)域都會(huì)發(fā)生;本節(jié)主要通過實(shí)例代碼演示各個(gè)內(nèi)存區(qū)域發(fā)生異常的情況,其中會(huì)使用到許多常用的虛擬機(jī)啟動(dòng)參數(shù)以便更好說明情況。(如何使用參數(shù)運(yùn)行程序這里不做描述)

1、java堆內(nèi)存溢出

堆內(nèi)存溢出發(fā)生在堆容量達(dá)到最大堆容量后創(chuàng)建對(duì)象情況下,在程序中只要不斷的創(chuàng)建對(duì)象,并且保證這些對(duì)象不會(huì)被垃圾回收:

/**
 * 虛擬機(jī)參數(shù):
 * -Xms20m 最小堆容量
 * -Xmx20m 最大堆容量
 * @author hwz
 *
 */
public class HeadOutOfMemoryError {

  public static void main(String[] args) {
    //使用容器保存對(duì)象,保證對(duì)象不被垃圾回收
    List<HeadOutOfMemoryError> listToHoldObj = new ArrayList<HeadOutOfMemoryError>();

    while(true) {
      //不斷創(chuàng)建對(duì)象并加入容器中
      listToHoldObj.add(new HeadOutOfMemoryError());
    }
  }
}

這里可以加上虛擬機(jī)參數(shù):-XX:HeapDumpOnOutOfMemoryError,在發(fā)送OOM異常的時(shí)候讓虛擬機(jī)轉(zhuǎn)儲(chǔ)當(dāng)前堆的快照文件,后續(xù)可以通過這個(gè)文件分詞異常問題,這個(gè)不做詳細(xì)描述,后續(xù)再寫個(gè)博客詳細(xì)描述使用MAT工具分析內(nèi)存問題。

2、虛擬機(jī)棧和本地方法棧溢出

在HotSpot虛擬機(jī)中,這兩個(gè)方法棧是沒有一起實(shí)現(xiàn)的,根據(jù)虛擬機(jī)規(guī)范,這兩塊內(nèi)存區(qū)域會(huì)發(fā)生這兩種異常:

1. 如果線程請(qǐng)求棧深度大于虛擬機(jī)允許的最大深度,拋出StackOverflowError異常;

2. 如果虛擬機(jī)在擴(kuò)展棧空間時(shí),無法申請(qǐng)大內(nèi)存空間,將拋出OutOfMemoryError異常;

這兩種情況實(shí)際上是存在重疊的:當(dāng)棧空間無法繼續(xù)分配是,到底是內(nèi)存太小還是已使用的棧深度太大,這個(gè)無法很好的區(qū)分。

使用兩種方式測試代碼

1. 使用-Xss參數(shù)減少棧大小,無限遞歸調(diào)用一個(gè)方法,無限加大棧深度:

/**
 * 虛擬機(jī)參數(shù):<br>
 * -Xss128k 棧容量
 * @author hwz
 *
 */
public class StackOverflowError {

  private int stackDeep = 1;

  /**
   * 無限遞歸,無限加大調(diào)用棧深度
   */
  public void recursiveInvoke() {
    stackDeep++;
    recursiveInvoke();
  }
  public static void main(String[] args) {
    StackOverflowError soe = new StackOverflowError();

    try {
      soe.recursiveInvoke();
    } catch (Throwable e) {
      System.out.println("stack deep = " + soe.stackDeep);
      throw e;
    }
  }
}

方法中定義大量本地變量,增加方法棧中本地變量表的長度,同樣無限遞歸調(diào)用:

/**
 * @author hwz
 *
 */
public class StackOOMError {

  private int stackDeep = 1;

  /**
   * 定義大量本地變量,增大棧中本地變量表
   * 無限遞歸,無限加大調(diào)用棧深度
   */
  public void recursiveInvoke() {
    Double i;
    Double i2;
    //.......此處省略大量變量定義
    stackDeep++;
    recursiveInvoke();
  }
  public static void main(String[] args) {
    StackOOMError soe = new StackOOMError();

    try {
      soe.recursiveInvoke();
    } catch (Throwable e) {
      System.out.println("stack deep = " + soe.stackDeep);
      throw e;
    }
  }
}

以上代碼測試說明,無論是幀棧太大還是虛擬機(jī)容量太小,當(dāng)內(nèi)存無法分配時(shí),拋出的都是StackOverflowError異常;

3、方法區(qū)和運(yùn)行時(shí)常量池溢出

這里先描述一下String的intern方法:如果字符串常量池已經(jīng)包含一個(gè)等于此String對(duì)象的字符串,則返回代表這個(gè)字符串的String對(duì)象,否則將此String對(duì)象添加到常量池中,并返回此String對(duì)象的引用;通過這個(gè)方法不斷在常量池中增加String對(duì)象,導(dǎo)致溢出:

/**
 * 虛擬機(jī)參數(shù):<br>
 * -XX:PermSize=10M 永久區(qū)大小
 * -XX:MaxPermSize=10M 永久區(qū)最大容量
 * @author hwz
 *
 */
public class RuntimeConstancePoolOOM {

  public static void main(String[] args) {

    //使用容器保存對(duì)象,保證對(duì)象不被垃圾回收
    List<String> list = new ArrayList<String>();

    //使用String.intern方法,增加常量池的對(duì)象
    for (int i=1; true; i++) {
      list.add(String.valueOf(i).intern());
    }
  }
}

但是這段測試代碼在JDK1.7下沒有發(fā)生運(yùn)行時(shí)常量池溢出,在JDK1.6倒是會(huì)發(fā)生,為此再寫一段測試代碼驗(yàn)證這個(gè)問題:

/**
 * String.intern方法在不同JDK下測試
 * @author hwz
 *
 */
public class StringInternTest {

  public static void main(String[] args) {

    String str1 = new StringBuilder("test").append("01").toString();
    System.out.println(str1.intern() == str1);

    String str2 = new StringBuilder("test").append("02").toString();
    System.out.println(str2.intern() == str2);
  }
}

在JDK1.6下運(yùn)行結(jié)果為:false、false;

在JDK1.7下運(yùn)行結(jié)果為:true、true;

原來在JDK1.6中,intern()方法把首次遇到的字符串實(shí)例復(fù)制到永久代,反回的是永久代中的實(shí)例的引用,而有StringBuilder創(chuàng)建的字符串實(shí)例在堆中,所以不相等;

而在JDK1.7中,intern()方法不會(huì)復(fù)制實(shí)例,只是在常量池記錄首次出現(xiàn)的實(shí)例的引用,因此intern返回的引用和StringBuilder創(chuàng)建的實(shí)例是同一個(gè),所以返回true;

所以常量池溢出的測試代碼不會(huì)發(fā)生常量池溢出異常,而是在不斷運(yùn)行后可能發(fā)生堆內(nèi)存不足溢出異常;

那要測試方法區(qū)溢出,只要不斷往方法區(qū)加入東西就行了,比如類名、訪問修飾符、常量池等。我們可以讓程序加載大量的類去不斷填充方法區(qū)從而導(dǎo)致溢出,這個(gè)我們使用CGLib直接操作字節(jié)碼生成大量動(dòng)態(tài)類:

/**
 * 方法區(qū)內(nèi)存溢出測試類
 * @author hwz
 *
 */
public class MethodAreaOOM {

  public static void main(String[] args) {
    //使用GCLib無限動(dòng)態(tài)創(chuàng)建子類
    while (true) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(MAOOMClass.class);
      enhancer.setUseCache(false);
      enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
          return proxy.invokeSuper(obj, args);
        }
      });
      enhancer.create();
    }
  }

  static class MAOOMClass {}
}

通過VisualVM觀察可以看到,JVM加載類的數(shù)量和PerGen的使用成直線上升:

4、直接內(nèi)存溢出

直接內(nèi)存的大小可以通過虛擬機(jī)參數(shù)設(shè)定:-XX:MaxDirectMemorySize,要使直接內(nèi)存溢出,只需要不斷的申請(qǐng)直接內(nèi)存即可,以下同Java NIO 中直接內(nèi)存緩存測試:

/**
 * 虛擬機(jī)參數(shù):<br>
 * -XX:MaxDirectMemorySize=30M 直接內(nèi)存大小
 * @author hwz
 *
 */
public class DirectMemoryOOm {

  public static void main(String[] args) {
    List<Buffer> buffers = new ArrayList<Buffer>();
    int i = 0;
    while (true) {
      //打印當(dāng)前第幾次
      System.out.println(++i);
      //通過不斷申請(qǐng)直接緩存區(qū)內(nèi)存消耗直接內(nèi)存
      buffers.add(ByteBuffer.allocateDirect(1024*1024)); //每次申請(qǐng)1M
    }
  }
}

在循環(huán)中,每次申請(qǐng)1M直接內(nèi)存,設(shè)置最大直接內(nèi)存為30M,程序運(yùn)行到31次時(shí)拋出異常:java.lang.OutOfMemoryError: Direct buffer memory

四、總結(jié)

以上就是本文的全部內(nèi)容,本文主要描述JVM中內(nèi)存的布局結(jié)構(gòu)、對(duì)象存儲(chǔ)和訪問已經(jīng)各個(gè)內(nèi)存區(qū)域可能出現(xiàn)的內(nèi)存異常;主要參考書目《深入理解Java虛擬機(jī)(第二版)》,如有不正確之處,還請(qǐng)?jiān)谠u(píng)論中指出;謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Scala入門之List使用詳解

    Scala入門之List使用詳解

    這篇文章主要介紹了Scala入門之List使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Java StringBuilder類相關(guān)知識(shí)總結(jié)

    Java StringBuilder類相關(guān)知識(shí)總結(jié)

    這篇文章主要介紹了Java StringBuilder類相關(guān)知識(shí)總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • 使用IDEA如何導(dǎo)入SpringBoot項(xiàng)目

    使用IDEA如何導(dǎo)入SpringBoot項(xiàng)目

    這篇文章主要介紹了使用IDEA如何導(dǎo)入SpringBoot項(xiàng)目問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,
    2023-12-12
  • Java中時(shí)間API的基本使用教程

    Java中時(shí)間API的基本使用教程

    這篇文章主要介紹了Java中時(shí)間API的基本使用教程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 詳解Java中Collector接口的組成

    詳解Java中Collector接口的組成

    今天給大家?guī)淼氖顷P(guān)于Java基礎(chǔ)的相關(guān)知識(shí),文章圍繞著Collector接口的組成展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • SpringMVC+Mysql實(shí)例詳解(附demo)

    SpringMVC+Mysql實(shí)例詳解(附demo)

    本篇文章主要介紹了SpringMVC+Mysql實(shí)例詳解(附demo),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。
    2016-12-12
  • Java超詳細(xì)精講數(shù)據(jù)結(jié)構(gòu)之bfs與雙端隊(duì)列

    Java超詳細(xì)精講數(shù)據(jù)結(jié)構(gòu)之bfs與雙端隊(duì)列

    廣搜BFS的基本思想是: 首先訪問初始點(diǎn)v并將其標(biāo)志為已經(jīng)訪問。接著通過鄰接關(guān)系將鄰接點(diǎn)入隊(duì)。然后每訪問過一個(gè)頂點(diǎn)則出隊(duì)。按照順序,訪問每一個(gè)頂點(diǎn)的所有未被訪問過的頂點(diǎn)直到所有的頂點(diǎn)均被訪問過。廣度優(yōu)先遍歷類似與層次遍歷
    2022-07-07
  • Java圖文分析之繼承內(nèi)存布局

    Java圖文分析之繼承內(nèi)存布局

    這篇文章主要介紹了Java圖文分析之繼承內(nèi)存布局,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的朋友可以參考一下
    2022-09-09
  • 如何在Java中使用標(biāo)準(zhǔn)庫創(chuàng)建臨時(shí)文件

    如何在Java中使用標(biāo)準(zhǔn)庫創(chuàng)建臨時(shí)文件

    有時(shí)候我們程序運(yùn)行時(shí)需要產(chǎn)生中間文件,但是這些文件只是臨時(shí)用途,并不做長久保存,我們可以使用臨時(shí)文件,不需要長久保存,這篇文章主要給大家介紹了關(guān)于如何在Java中使用標(biāo)準(zhǔn)庫創(chuàng)建臨時(shí)文件的相關(guān)資料,需要的朋友可以參考下
    2023-10-10
  • Java 程序員必備的 Intellij IDEA 插件

    Java 程序員必備的 Intellij IDEA 插件

    java插件十分方便實(shí)用,以下是我用過不錯(cuò)的Intellij插件,當(dāng)然還有很多插件也都不錯(cuò),下面將我覺得不錯(cuò)的插件分享給大家,希望能幫到大家
    2018-09-09

最新評(píng)論