深刻了解Java虛擬機(1)java內存區域與內存溢出異常

java內存區域與內存溢出異常

1、運行時數據區域

  

 

  1.程序計數器:線程私有,用於存儲當前所執行的指令位置java

  2.Java虛擬機棧:線程私有,描敘Java方法執行模型;執行方法時都會建立一個棧幀,存儲局部變量,基本類型變量,引用等信息spring

  3.Java本地方法棧:線程私有,爲虛擬機使用到的Native方法服務安全

  4.Java堆:線程共享,是垃圾收集器的主要工做地方;存儲對象實例等app

  5.方法區:線程共享;存儲類信息,常量,靜態變量等佈局

    運行時常量:存放編譯時生成的各類字面量和符號引用ui

  6.直接內存:機器的內存spa

2、虛擬機對象

  1.對象的建立

  • 先檢查常量池可否定位到此類的符號引用,並檢查類是否已經加載初始化,不然要先執行加載過程;
  • 爲對象分配內存:計算空間並從堆中劃分一塊連續或不連續的區域;使用的是cas+失敗重試,避免線程安全問題(由於對象建立十分頻繁,不知道當前內存有沒有被分配出去)
  • 初始化內存空間:將分配的內存空間初始化0值
  • 設置對象基本信息:元數據、hash碼、gc等
  • 執行java的init初始化:

  2.對象的內存佈局

    對象頭:存儲對象的hash碼、鎖狀態等 和 類型指針(對象所指向類的元數據)線程

    實例數據:對象真正存儲的信息3d

    對齊填充:填充符合規則代理

  3.對象的訪問定位

    對象的訪問,經過java棧上的reference數據,它維護了一個指向對象的引用

    訪問方式:句柄和直接訪問

      

      句柄:堆中維護句柄池,reference指向句柄,句柄中包含了對象實例數據和類型數據的地址信息

        移動方便,直接修改句柄中的實例數據便可;開銷大,多了一次指針定位

     

      直接:reference直接指向對象地址

        速度快

3、實戰OutofMemoryERROR

  1.java堆溢出

    參數:-Xms堆最小值;-Xmx堆最大值;-XX:+HeapDumpOnOutOfMemoryError出現溢出時內存快照分析

    堆中存放的是對象:能夠建立大量對象來實現堆溢出:heap space

  2.棧溢出

    參數:-Xss設置棧值

    棧深度,能夠經過無限遞歸增長棧深度、或建立大量線程實現

//遞歸來StackOverFlower
public class JavaVMStackSOF {
    private int stackLength = 1public void stackLeak(){
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args)throws Throwable{
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch(Throwable e){
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

  3.方法區和常量池溢出

    參數:-XX:PermSize方法區大小;-XX:MaxPermSize方法區最大大小

      在JDK1.6前,能夠經過建立大量的String,虛擬機會複製對象放入常量池,從而溢出

      在1.7及之後,不能夠這樣,由於虛擬機只會在常量池中保存首次出現此對象時對象的引用

      方法區的溢出:方法區保存的是類的信息,經過產生大量的動態類來溢出,如spring其實也是經過動態代理產生的類

public class JavaMethodAreaOOM{
    public static void main(String[]args){
        whiletrue){//建立大量的動態類,動態代理OOMObject
            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{
    }
}

 

  String.intern()是一個Native方法,它的做用是:若是字符串常量池中已經包含一個等於此String對象的字符串,則返回表明池中這個字符串的String對象;不然,將此String對象包含的字符串添加到常量池中,而且返回此String對象的引用

  JDK6及之前:方法區(永久代)是單獨的,常量池在方法區內

  JDK7:去永久代

public class RuntimeConstantPoolOOM{
    public static void main(String[]args){
        String str1=new StringBuilder("計算機").append("軟件").toString();
        System.out.println(str1.intern()==str1);
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern()==str2);
    }
} 

  這段代碼在JDK 1.6中運行,會獲得兩個false,而在JDK 1.7中運行,會獲得一個true和一個false。

  產生差別的緣由是:在JDK 1.6中,intern()方法會把首次遇到的字符串實例複製到永久代中,返回的也是永久代中這個字符串實例的引用,而由StringBuilder建立的字符串實例在Java堆上,因此必然不是同一個引用,將返回false。

  而JDK 1.7:intern()實現不會再複製實例,只是在常量池中記錄首次出現的實例引用,所以intern()返回的引用和由StringBuilder建立的那個字符串實例是同一個。

  對str2比較返回false是由於「java」這個字符串在執行StringBuilder.toString()以前已經出現過,字符串常量池中已經有它的引用了,不符合「首次出現」的原則,而「計算機軟件」這個字符串則是首次出現的,所以返回true

  注意:1.7及之後保存的是首次出現的引用;理解上面的分析

   4.本機直接內存

    參數:-XX:MaxDirectMemorySize直接內存大小;默認==最大堆內存

相關文章
相關標籤/搜索