淺析Java內(nèi)存分配

線程共享區(qū)域:方法區(qū),堆
線程私有區(qū)域:程序計(jì)數(shù)器,本地方法棧,虛擬機(jī)棧


內(nèi)存.png

虛擬機(jī)棧

線程私有,生命周期與線程一致。每調(diào)用一個(gè)方法就可以創(chuàng)建一個(gè)新的棧幀,棧幀中存放局部變量表(基本類(lèi)型數(shù)據(jù)和對(duì)象引用)、操作數(shù)棧和方法出口等信息。大小可動(dòng)態(tài)擴(kuò)展。
在編譯期間分配內(nèi)存
當(dāng)棧調(diào)用深度大于JVM所允許的范圍,會(huì)拋出StackOverflowError的錯(cuò)誤;當(dāng)申請(qǐng)不到空間時(shí),會(huì)拋出 OutOfMemoryError。

本地方法棧

空間較小,用于獲取要執(zhí)行的字節(jié)碼指令。

PC 寄存器:

也叫程序計(jì)數(shù)器。JVM支持多個(gè)線程同時(shí)運(yùn)行,每個(gè)線程都有自己的程序計(jì)數(shù)器。倘若當(dāng)前執(zhí)行的是 JVM 的方法,則該寄存器中保存當(dāng)前執(zhí)行指令的地址;倘若執(zhí)行的是native 方法,則PC寄存器中為空。

虛擬機(jī)中內(nèi)存最大的一塊,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,用于存儲(chǔ)方法實(shí)例??梢詣澐殖龆鄠€(gè)線程私有的緩沖區(qū),可以處于物理上不連續(xù),邏輯上連續(xù)的空間中。
常量池位于堆中。JVM為每個(gè)已加載的類(lèi)型維護(hù)一個(gè)常量池。存儲(chǔ)直接常量(基本類(lèi)型 、String)和對(duì)其他類(lèi)型方法、字段的符號(hào)引用。在Java動(dòng)態(tài)鏈接中起核心作用。

方法區(qū)

主要用于存儲(chǔ)類(lèi)的信息、常量池、方法數(shù)據(jù)、方法代碼等。方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進(jìn)行區(qū)分,通常又叫“非堆”。

小結(jié):

1 實(shí)例位于棧中,對(duì)象在堆中。操作實(shí)例,實(shí)際上是通過(guò)實(shí)例的指針間接操作對(duì)象。
2 棧中的數(shù)據(jù)和堆中的數(shù)據(jù)銷(xiāo)毀不同步。
方法一但結(jié)束,棧中的局部變量立即銷(xiāo)毀,然鵝可能還有其他變量指向堆中的對(duì)象,知道棧中沒(méi)有任何對(duì)象指向它,在垃圾回收時(shí),才能被銷(xiāo)毀。
3 每個(gè)JVM實(shí)例都有自己的內(nèi)存區(qū)域,且互不影響。(這些內(nèi)存區(qū)域都是線程共享的)
4 類(lèi)的成員變量在不同的對(duì)象中個(gè)不相同,存儲(chǔ)在堆中的對(duì)象里。而方法這是使用時(shí)才壓入棧中。


普通Java對(duì)象的創(chuàng)建過(guò)程

虛擬機(jī)在遇到new指令時(shí),

  • 檢查該指令參數(shù)是否可以在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并檢查它是否被加載、解析或者初始化過(guò),如果沒(méi)有字執(zhí)行類(lèi)加載。
  • 為新生對(duì)象分配內(nèi)存,即將一塊確定大小的內(nèi)存從Java堆中劃分出來(lái)(在類(lèi)加載時(shí)確定其大?。?br> >指針碰撞: Java堆中的內(nèi)存絕對(duì)規(guī)整,用過(guò)的在一邊,未用的在另一邊,中間放置一個(gè)指針作為指示器。分配內(nèi)存即將指針移向所用空間的距離。
    >空閑列表:Java堆中的內(nèi)存并不規(guī)整,虛擬機(jī)維護(hù)一個(gè)列表類(lèi)似記錄哪塊內(nèi)存是用過(guò)的哪塊是沒(méi)有的,分配時(shí)從中找到一個(gè)足夠大的區(qū)域劃分給對(duì)象,并且更新列表。
  • 解決并發(fā)產(chǎn)生的問(wèn)題
    >在分配內(nèi)存空間時(shí)做同步處理,即采用CAS和失敗重試的方式保證更新時(shí),操作的原子性。
    >將該動(dòng)作劃分在不同的空間中進(jìn)行。
  • 將分配的內(nèi)存空間初始化為0
  • 對(duì)對(duì)象進(jìn)行必要設(shè)置(此時(shí),虛擬機(jī)看來(lái),對(duì)象已經(jīng)產(chǎn)生,但是對(duì)于程序而言,還沒(méi)有產(chǎn)生)
  • 執(zhí)行<init>()

類(lèi)加載的過(guò)程

案例分析:

class Singleton{
  private static Singleton singleton=new Singleton();
  public static int count_1;//1
  public static int count_2=0;//0
  static{
     count_1++;
      count_2++;
  }
  private Singleton(){
    count_1++://1
    count_2++;//1
  }
  public static Singleton getInstance(){
    return singleton;
  }
}
public class ClassLoaderProcess{
  public static void main(String[] args){
    System,out.println(count_1);//2
    System,out.println(count_2);//1
  }
}
  • 準(zhǔn)備階段:private Singleton()方法將count_1和count_2賦為零值
  • 初始化階段:Java虛擬機(jī)按順序執(zhí)行
    >實(shí)例化private Singleton()方法,count_1++;//1、 count_2++;//1
    >按順序執(zhí)行static代碼,靜態(tài)變量的值賦為代碼中的初始化值:count_1;//1、count_2=0;
    >執(zhí)行靜態(tài)代碼塊中的代碼,即自增,count_1=2;、count_2=1;

具體內(nèi)容可以看下面的鏈接,詳細(xì)的不能再詳細(xì)的那種

http://blog.csdn.net/u013256816/article/details/50829596

Java虛擬機(jī)類(lèi)加載機(jī)制——案例分析

http://www.importnew.com/18566.html


內(nèi)存溢出

  • Java堆內(nèi)存溢出
public class HeapOOM{
  static class OOMObject{}
  public static void main(String[] args){
    List<OOMObject> list=new ArrayList<OOMObject>();
    while(true){
      list.add(new OOMObject);
    }
  }  
}

Java堆用來(lái)存儲(chǔ)對(duì)象實(shí)例,只要不斷的創(chuàng)建對(duì)象,并且保證GCRoots到對(duì)象之間都有可達(dá)路徑來(lái)避免垃圾回收機(jī)制來(lái)清楚這些對(duì)象,就會(huì)造成OOM

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

如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverFlowError;
減少棧內(nèi)存容量或者定義大量的本地變量(導(dǎo)致棧幀變大)會(huì)導(dǎo)致以上錯(cuò)誤

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

如果虛擬機(jī)在擴(kuò)展棧的時(shí)候,無(wú)法申請(qǐng)到足夠的內(nèi)存空間,則拋出OutOfMemoryError。
多線程中,每個(gè)線程分配的棧內(nèi)存越大,越容易產(chǎn)生內(nèi)存溢出。
解決方案:減少最大堆和減少棧容量來(lái)?yè)Q取更多的線程。

public class JavaVMStackOOM{
  private void dontStop(){
    while(true){}
  }
  public void stackLeakByThread(){
    while(true){
      Thread thread=new Thread(new Runnable(){
          public void run(){
            dontStop();
          }
        }
        
    );
    thread.start();
    }
    stackLength++;
    stackLeak();
  }
  public static void main(String[] args){
    JavaVMStackOOM oom=new JavaVMStackOOM();
    oom.stackLeakByThread();
   }
}
  • 方法區(qū)和運(yùn)行時(shí)常量池溢出
  • 本機(jī)直接內(nèi)存溢出

內(nèi)存泄露

  • 只要類(lèi)自己管理內(nèi)存需要警惕內(nèi)存泄露問(wèn)題。
    一旦元素被釋放掉,該元素中包含的任何引用都要被清空
    E.G.Stack類(lèi)自己管理內(nèi)存,存儲(chǔ)池中包含了elements數(shù)組(對(duì)象引用單元而不是對(duì)象本身的元素)的元素,數(shù)組活動(dòng)區(qū)域中的元素時(shí)已經(jīng)分配好的,而數(shù)據(jù)其余部分的元素是自由的。但是垃圾回收器對(duì)他們一視同仁,即elements的所有元素都同等有效。

Solution:在數(shù)組元素變成非活動(dòng)部分時(shí),程序員手動(dòng)清除這些元素

  • 緩存也會(huì)造成內(nèi)存泄露
    一旦對(duì)象進(jìn)入緩存就很容易被遺忘

Solution_1:用WeakHashMap代表緩存。只要在緩存之外存在對(duì)某個(gè)對(duì)象的key的引用,該項(xiàng)就有意義。當(dāng)緩存中的項(xiàng)過(guò)期后,他們會(huì)自動(dòng)被刪除。
當(dāng)索要緩存的項(xiàng)的生命周期是由該鍵的外部引用決定,而不是緩存項(xiàng)的值決定時(shí),WeakHashMap才有用

Solution_2:由后臺(tái)線程定期清除或者在給緩存中添加新的條目的時(shí)候順便清理。(LinkedHashMap利用其removeEldestEntry實(shí)現(xiàn))

Solution_3:對(duì)于復(fù)雜內(nèi)存,java.lang.ref解決

  • 監(jiān)聽(tīng)器與其他回調(diào)
    E.G.如果實(shí)現(xiàn)了一個(gè)api,客戶端在改api調(diào)用中注冊(cè)回調(diào),卻沒(méi)有顯示的取消,除非采取某些動(dòng)作,否則他們會(huì)繼續(xù)聚集。

Solution:保存其弱引用,即將他們作為WeakHashMap中的鍵即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容