JVM自動(dòng)內(nèi)存管理之二

棧異常
  1. 如果線程請(qǐng)求分配的棧容量超過(guò)JVM允許的最大容量時(shí),會(huì)拋出StackOverflowError異常
  2. 如果java虛擬機(jī)椨岸ぃ可以動(dòng)態(tài)擴(kuò)展寸认,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過(guò)纵顾,但是無(wú)法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展,會(huì)拋出OutOfMemoryError
  3. 如果創(chuàng)建新線程時(shí)沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的java虛擬機(jī)棧间唉,也會(huì)拋出OutOfMemoryError
public class JavaVMStackSOF{

    private int stackLength = 1;

    public void stackLeak(){
        stackLength++;
        stackLeak(); // 遞歸調(diào)用自己
    }

    public static void main(String[] args){
        JavaVMStackSOF sof = new JavaVMStackSOF();
        try{
            sof.stackLeak();
        }catch(Throwable e){
            System.out.println("stack length=" + sof.stackLength);
            throw e;
        }
    }
}

執(zhí)行結(jié)果:

bash> java JavaVMStackSOF -Xss128K

stack length=18357
Exception in thread "main" java.lang.StackOverflowError
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)

而一直分配新的線程绞灼,死循環(huán),就會(huì)導(dǎo)致OOM呈野,這里就不演示了低矮。

所有的對(duì)象依然在堆中,局部變量表际跪、操作數(shù)棧中只有對(duì)應(yīng)的reference引用商佛,以及基礎(chǔ)數(shù)據(jù)類型。

  • 堆是所有線程全局共享的內(nèi)存空間姆打,一般也是jvm內(nèi)存區(qū)域中最大的一部分良姆,幾乎所有對(duì)象都在其中。

  • 是GC回收的主要區(qū)域幔戏,所以也叫g(shù)c堆玛追。

  • 為了提高不同線程在堆中內(nèi)存分配時(shí)的效率,減少?zèng)_突闲延,有的jvm實(shí)現(xiàn)會(huì)為每個(gè)線程在創(chuàng)建時(shí)同時(shí)創(chuàng)建TLAB痊剖,即threadlocal allocating buffer。線程在各自的tlab中分配對(duì)象垒玲,當(dāng)tlab中內(nèi)存用完時(shí)陆馁,才會(huì)加鎖,然后去堆中再去申請(qǐng)新的內(nèi)存合愈。

  • java堆還有一種劃分方式叮贩,就是新生代、老年代這種分代劃分佛析。

  • java堆可以在物理上不連續(xù)的空間上分配益老,只要邏輯上看起來(lái)是連續(xù)的即可〈缒可以使用-Xmx捺萌,-Xms來(lái)設(shè)定堆空間的最大和最小值。

  • 當(dāng)java堆空間已經(jīng)不足以分配一個(gè)新對(duì)象膘茎,并且無(wú)法擴(kuò)展新空間桃纯,即使垃圾回收也無(wú)法會(huì)受到足夠的空間酷誓,就會(huì)報(bào)OutOfMemoryError異常。

堆和棧的關(guān)聯(lián)
Snip20180510_13.png

如上圖所示:

  • obj只是一個(gè)引用慈参,存放在執(zhí)行這條語(yǔ)句的線程的java虛擬機(jī)棧中呛牲,其中一個(gè)slot存儲(chǔ)了這個(gè)reference。
  • obj指向了java堆中的內(nèi)存地址驮配,這個(gè)地址對(duì)應(yīng)object對(duì)象。
  • Object對(duì)象頭中一般會(huì)存放當(dāng)前實(shí)例對(duì)應(yīng)的類型信息着茸,用于從方法區(qū)中尋找到對(duì)應(yīng)的類信息壮锻。
int a = 1;

int[] array = new int[] {1,2};

a是局部變量,而且是基本數(shù)據(jù)類型涮阔,會(huì)直接存在java虛擬機(jī)棧中猜绣,不過(guò)這個(gè)其實(shí)算是jvm提供的一種性能優(yōu)化,引用類型還是會(huì)放在堆中的敬特,只是其引用在棧中掰邢,尋址會(huì)存在一定的性能消耗。

array是個(gè)對(duì)象伟阔,array會(huì)作為一個(gè)引用存在棧中辣之,而對(duì)應(yīng)的數(shù)組對(duì)象會(huì)存儲(chǔ)在堆中。

Java堆內(nèi)存溢出
import java.util.ArrayList;
import java.util.List;

/**
    -Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM{

    static class MyObect{}

    public static void main(String[] args){

        List<MyObect> list = new ArrayList<MyObect>();

        for(;;){
            list.add(new MyObect());
        }
    }
}

執(zhí)行的時(shí)候帶上jvm參數(shù)皱炉。會(huì)報(bào)錯(cuò)OutOfMemoryError怀估。

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2173.hprof ...
Heap dump file created [27844982 bytes in 0.135 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)
    at HeapOOM.main(HeapOOM.java:16)

Process finished with exit code 1

后面詳細(xì)說(shuō)明對(duì)應(yīng)的調(diào)試。

方法區(qū)

  1. 方法區(qū)也是各個(gè)線程共享的合搅。
  2. 用于存儲(chǔ)已經(jīng)被jvm加載到方法區(qū)中的類型信息多搀。
  3. gc效率很低,一般用于運(yùn)行時(shí)常量池的數(shù)據(jù)回收和類的卸載灾部。
運(yùn)行時(shí)常量池
  1. 是方法區(qū)的一個(gè)組成部分
  2. 存儲(chǔ)Java類文件中常量信息康铭,用于存儲(chǔ)編譯期就生成好的字面量和符號(hào)引用。這部分信息在類被加載到方法區(qū)支行赌髓,會(huì)存入運(yùn)行時(shí)常量池从藤。
  3. ?存在運(yùn)行期間生成的常量,比如string的intern方法對(duì)應(yīng)的常量春弥。
永久代—>方法區(qū)
  1. jdk6之前呛哟,hotspot使用永久代來(lái)實(shí)現(xiàn)方法去。
  2. jdk7中匿沛,開(kāi)始移除永久代:
    1. 符號(hào)表被移入native heap中
    2. 字符串常量和類的靜態(tài)引用被移到j(luò)ava heap中
  3. jdk8中扫责,metaspace替代永久代,metaspace是在native heap中的逃呼。

演示代碼:

public class RuntimeConstantPoolChange {

    public static void main(String[] args){

        String str1 = new StringBuilder("alan").append("jin").toString();
        System.out.println(str1 == str1.intern());  // intern:初次鳖孤,如果不存在者娱,會(huì)加入到常量池中,但是返回的是本身的reference苏揣,返回true

        // 如果存在黄鳍,則返回的是方法區(qū)中地址,而str1返回的是堆中地址,所以下面兩個(gè)輸出全是false

        String str2 = new StringBuilder("alan").toString();
        System.out.println(str2.intern() == str2); // false

        String str3 = new StringBuilder("java").toString();  // java 關(guān)鍵字已經(jīng)存在平匈,而且是在方法區(qū)中框沟,不在堆中,false
        System.out.println(str3.intern() == str3);

    }
}

方法區(qū)中OOM的代碼:

import java.util.ArrayList;
import java.util.List;

/**
 * VM args: -Xmx10m -Xms10m
 */
public class RuntimeConstantPoolOOM {

    public static void main(String[] args){

        List<String> strings = new ArrayList<>();

        int i = 0;

        // 在1.6版本中增炭,int的取值范圍2的31次方忍燥,足夠撐滿永久代,所以會(huì)oom
        // 在1.7及以上的jdk版本中隙姿,運(yùn)行時(shí)常量池在java heap中梅垄,可以永久執(zhí)行下去。但是如果java heap太小输玷,會(huì)廚下以下異常:
        while (true){
            strings.add(String.valueOf(i++).intern());
        }
    }
}

GC overhead limt exceed

檢查是Hotspot VM 1.6定義的一個(gè)策略队丝,通過(guò)統(tǒng)計(jì)GC時(shí)間來(lái)預(yù)測(cè)是否要OOM了,提前拋出異常欲鹏,防止OOM發(fā)生机久。Sun 官方對(duì)此的定義是:并行/并發(fā)回收器在GC回收時(shí)間過(guò)長(zhǎng)時(shí)會(huì)拋出OutOfMemroyError。過(guò)長(zhǎng)的定義是貌虾,超過(guò)98%的時(shí)間用來(lái)做GC并且回收了不到2%的堆內(nèi)存吞加。用來(lái)避免內(nèi)存過(guò)小造成應(yīng)用不能正常工作。

方法區(qū)又分成兩個(gè)部分尽狠,PermGen和CodeCache衔憨。其中PermGen存放java類的相關(guān)信息,如靜態(tài)變量袄膏、成員方法和抽象方法等践图。codecache存放JIT編譯后的本地代碼。沉馆?native代碼么码党?

直接內(nèi)存

  1. 直接內(nèi)存并不是JVM運(yùn)行時(shí)內(nèi)存的一部分

  2. 堆外內(nèi)存,NIO被引入斥黑,目的是為了避免java堆和native堆中來(lái)回復(fù)制數(shù)據(jù)帶來(lái)的性能損耗揖盘。

  3. 通過(guò)一個(gè)存儲(chǔ)在java heap中的DirectByteBuffer對(duì)象引用(通過(guò)虛引用(Phantom Reference)來(lái)實(shí)現(xiàn)堆外內(nèi)存的釋放)來(lái)管理native heap中的內(nèi)存空間。

  4. 全局共享的內(nèi)存區(qū)域锌奴,能夠被管理兽狭,但是檢測(cè)手段上會(huì)比較簡(jiǎn)陋

  5. 會(huì)出現(xiàn)OOM

    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    import java.nio.ByteBuffer;
    
    /**
     * -Xmx20M -XX:MaxDirectMemorySize=10M
     */
    public class DirectByteBufferOOM {
    
        private static final int size = 1024 * 1024 * 128;  // 128M
    
        public static void main(String[] args) throws IllegalAccessException {
    
            Field unsafeField = Unsafe.class.getDeclaredFields()[0]; // 獲取unsafe對(duì)象
    
            unsafeField.setAccessible(true); // 設(shè)置操作權(quán)限
    
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
    
            System.out.println(sun.misc.VM.maxDirectMemory());
    
            while (true){
                // unsafe.allocateMemory 就是DirectByteBuffer分配內(nèi)存時(shí)使用到的方法,不建議直接使用,只是為了展示
                // unsafe.allocateMemory(size);
    
                // 以上代碼無(wú)法產(chǎn)生OOM,但是下面的方法可以:
                ByteBuffer.allocateDirect(size);
    
                // As the original answer says:
                // Unsafe.allocateMemory() is a wrapper around os::malloc which doesn't care about any memory limits imposed by the VM.
    
                //ByteBuffer.allocateDirect() will call this method but before that,
                // it will call Bits.reserveMemory() (In my version of Java 7: DirectByteBuffer.java:123)
                // which checks the memory usage of the process and throws the exception which you mention.
            }
    
        }
    
    }
    
    

    產(chǎn)生的OOM異常如下:

    10485760
    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
     at java.nio.Bits.reserveMemory(Bits.java:694)
     at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
     at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
     at DirectByteBufferOOM.main(DirectByteBufferOOM.java:28)
    
    

    可以看到箕慧,如果是堆外內(nèi)存OOM服球,會(huì)顯示Direct Buffer Memory字樣,或者如果沒(méi)有明確的指示颠焦,但是dump出來(lái)的內(nèi)存很小斩熊,而且代碼中直接或者間接使用了NIO,那么也大概率是堆外內(nèi)存惹的禍伐庭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粉渠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子似忧,更是在濱河造成了極大的恐慌渣叛,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盯捌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蘑秽,警方通過(guò)查閱死者的電腦和手機(jī)饺著,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肠牲,“玉大人幼衰,你說(shuō)我怎么就攤上這事∽忽ǎ” “怎么了渡嚣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肥印。 經(jīng)常有香客問(wèn)我识椰,道長(zhǎng),這世上最難降的妖魔是什么深碱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任腹鹉,我火速辦了婚禮,結(jié)果婚禮上敷硅,老公的妹妹穿的比我還像新娘功咒。我一直安慰自己,他們只是感情好绞蹦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布力奋。 她就那樣靜靜地躺著,像睡著了一般幽七。 火紅的嫁衣襯著肌膚如雪景殷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音滨彻,去河邊找鬼藕届。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亭饵,可吹牛的內(nèi)容都是我干的休偶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辜羊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踏兜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起八秃,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碱妆,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后昔驱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疹尾,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年骤肛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纳本。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腋颠,死狀恐怖繁成,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淑玫,我是刑警寧澤巾腕,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站絮蒿,受9級(jí)特大地震影響尊搬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歌径,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一毁嗦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧回铛,春花似錦狗准、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至验残,卻和暖如春捞附,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工鸟召, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胆绊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓欧募,卻偏偏與公主長(zhǎng)得像压状,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子跟继,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容