# jvm了解一下~

參考資料《深入理解java虛擬機(jī)》

java內(nèi)存區(qū)域

運(yùn)行時數(shù)據(jù)區(qū)域

image
  1. 程序計數(shù)器 :可以看成是當(dāng)前線程所執(zhí)行字節(jié)碼的行號指示器。
    • 每個線程都需要一個獨(dú)立的程序計數(shù)器,所以是私有的。(java虛擬機(jī)多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式)
    • 如果線程執(zhí)行的是java方法,計數(shù)器記錄的是字節(jié)碼指令的地址;如果是native方法,計數(shù)器為空(uundifined),是唯一沒有outOfMemoryError的區(qū)域。
      • native是本地方法,和平臺有關(guān),需要借助c語言。

  2. 虛擬機(jī)棧 :線程私有的,生命周期和線程相同。
    • 描述的是java方法執(zhí)行的內(nèi)存模型。每個方法在執(zhí)行時都會創(chuàng)建一個棧幀,用來記錄局部變量、動態(tài)鏈接,方法出口等。每個方法的執(zhí)行 從開始到介紹就是一個棧幀從虛擬機(jī)棧入棧到出棧的過程。
      • 什么是棧幀呢?棧幀可以理解為一個方法的運(yùn)行空間。它主要由兩部分構(gòu)成,一部分是局部變量表,方法中定義的局部變量以及方法的參數(shù)就存放在這張表中;另一部分是操作數(shù)棧,用來存放操作數(shù)。

    • 這個區(qū)域規(guī)定了兩種異常
      • 如果線程請求的棧深度大于虛擬機(jī)允許的棧深度,則拋出 stackOverFlowError,比如遞歸調(diào)用
      • 如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展,但是擴(kuò)展時申請不到足夠的內(nèi)存,則拋出OutOfMemoryError,比如這個線程運(yùn)行時創(chuàng)建大量的類。
  3. 本地方法棧 和虛擬機(jī)棧類似。區(qū)別是虛擬機(jī)棧為執(zhí)行java方法服務(wù),本地方法棧為運(yùn)行native服務(wù)。
  4. java堆 重點(diǎn)來了~ 下面重點(diǎn)分析
    • 是java虛擬機(jī)中內(nèi)存區(qū)域最大的一塊
    • 是被所有線程共享的區(qū)域,虛擬機(jī)啟動時創(chuàng)建。
    • 幾乎所有的對象實例都會在這分配空間
    • 可以是物理上不連續(xù)的區(qū)域,只要是邏輯上連續(xù)即可
    • 如果堆中沒有內(nèi)存可以分配,并且不能擴(kuò)展的話,拋出 OutOfMemoryError異常
  5. 方法區(qū) non-heap (非堆)
    • 是各個線程的共享區(qū)域
    • 用于存放虛擬機(jī)加載的類信息,常量,靜態(tài)變量、即時編譯器編譯的代碼等。
    • 并不能完全等同于永久代(permanent generation)
    • 垃圾回收在這比較少出現(xiàn),回收目標(biāo)是常量池和對類型的卸載
  6. 運(yùn)行時常量池,是方法區(qū)的一部分。
    • class文件除了記錄類的版本,字段、方法等,還有常量池,用于存放編譯期生成的字面量和符號引用,在類的加載后進(jìn)入該區(qū)域。
    • 具有動態(tài)性,運(yùn)行期間也可以將新的常量放入池中。比如string類中的 intern()方法

      string.intern() : 如果字符串常量池中已經(jīng)包含一個等于string對象的字符串,則返回池中這個字符串的對象,否則,將此字符串對象包含的字符串放入常量池中,并返回次string對象的引用。

    • 受到方法區(qū)的限制,當(dāng)不能申請內(nèi)存時,拋出OutOfMemoryError異常
  7. 直接內(nèi)存:不屬于虛擬機(jī)內(nèi)存,但是有可能導(dǎo)致OutOfMemoryError異常

虛擬機(jī)對象

……

OutOfMemoryError異常分析

堆溢出

 * VM Args: -Xms(堆的最小值)20m -Xmx(堆的最大值)20m:都設(shè)置成20m  防止堆內(nèi)存自動擴(kuò)展
 * - XX:+HeapDumpOnOutOfMemoryError  oom時生成dump文件
 
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}
運(yùn)行結(jié)果:
Exception in thread "main" Heap dump file created [2314620069 bytes in 32.813 secs]
java.lang.OutOfMemoryError: Java heap space
  • 內(nèi)存泄露
    • 查看泄露對象到gc root的引用鏈(不太會找 哈哈)
  • 內(nèi)存溢出
    • 檢查堆參數(shù) xms xmx,是否還能調(diào)大。檢查代碼是否存在對象生命周期過長等情況。

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

單線程

==Xss:設(shè)置每個線程的堆棧大小==

/**
 * hotspot虛擬機(jī)不區(qū)分虛擬機(jī)棧和本地方法棧 所以只設(shè)置xss
 * VM Args:-Xss128k
 */

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;
        }
    }
}

運(yùn)行結(jié)果:
stack length: 978
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
……
  • 如果線程請求的棧深度大于虛擬機(jī)允許的棧深度,則拋出 stackOverFlowError
  • 虛擬機(jī)棧擴(kuò)展棧時申請不到足夠的內(nèi)存,則拋出OutOfMemoryError

當(dāng)??臻g無法分配時,是已使用的??臻g太大,還是內(nèi)存空間太???不管是調(diào)用xss減少棧內(nèi)存容量,還是增大本方法中本地變量表的長度,當(dāng)內(nèi)存無法分配時,都拋出stackOverFlowError異常。

多線程
  • 多線程下的內(nèi)存溢出,與??臻g是否大不存在任何聯(lián)系
  • 同等物理內(nèi)存下,為棧每個??臻g分配的內(nèi)存越大,可以創(chuàng)建的線程就越少。
  • 如果不能減少線程數(shù),就只能減少最大堆(增加虛擬機(jī)棧的內(nèi)存?)和減少棧容量來獲得更多線程。
public class JavaVMStackOOM {

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

    // 多線程方式造成棧內(nèi)存溢出 OutOfMemoryError
    public void stackLeakByThread() {
        while(true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

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

方法區(qū)用于存放class的信息,如類名、修飾符、常量池、字段描述等。對于該區(qū)域的測試,==思路就是運(yùn)行時產(chǎn)生大量的類去填滿方法區(qū),直到溢出。==

  • CGLib動態(tài)生成類導(dǎo)致的方法區(qū)溢出
/** 
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while(true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method m, Object[] objs, MethodProxy proxy) throws Throwable {
                    // TODO Auto-generated method stub
                    return proxy.invokeSuper(obj, objs);
                }
            });
            enhancer.create();
        }
    }
}
運(yùn)行結(jié)果:
Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    ......

對程序的講解參考 CGLIB enhancer講解

  • 在經(jīng)常動態(tài)生成大量class的應(yīng)用中,要特別注意類的回收情況,

  • CGLib:Code Generation Library

    • CGLIB是一個強(qiáng)大的、高性能的代碼生成庫。其被廣泛應(yīng)用于AOP框架(Spring、dynaop)中,用以提供方法攔截操作
    • 原理:動態(tài)生成一個要代理類的子類,之類要實現(xiàn)代理類的所有方法(除了final修飾的)。在之類中利用方法攔截技術(shù)攔截所有代理類的方法的調(diào)用,順勢織入橫切邏輯。
  • CGLIB和Java動態(tài)代理的區(qū)別

    • Java動態(tài)代理只能夠?qū)涌谶M(jìn)行代理,不能對普通的類進(jìn)行代理(因為所有生成的代理類的父類為Proxy,Java類繼承機(jī)制不允許多重繼承);CGLIB能夠代理普通類;
    • Java動態(tài)代理使用Java原生的反射API進(jìn)行操作,在生成類上比較高效;CGLIB使用ASM框架直接對字節(jié)碼進(jìn)行操作,在類的執(zhí)行過程中比較高效

本地內(nèi)存直接溢出(感覺不常用,略,有興趣可以參考《深入理解java虛擬機(jī) 2.4.4小節(jié)》)

垃圾收集器和內(nèi)存分配

對象還存活嗎?
  • 判斷對象是否存活,并不是給對象添加一個引用計數(shù)器。盡管有時候效率還是很高,java虛擬機(jī)并沒有采用,因為沒辦法解決對象之間相互循環(huán)引用的問題。
  • java中是采用可達(dá)性分析算法來判斷對象是否存活的。
    • 算法思想:通過GC root的對象作為起點(diǎn),從這個節(jié)點(diǎn)向下搜索,搜索經(jīng)過的路徑叫做引用鏈,當(dāng)一個對象到GC root沒有任何引用鏈項鏈,就認(rèn)為對象是不可用的。


      image
    • java中GC root對象包括以下幾種
      • 虛擬機(jī)棧中引用的對象
      • 方法區(qū)中類靜態(tài)屬性引用的對象
      • 方法區(qū)中常量引用的對象
      • 本地方法棧中native引用的對象
引用
  • 強(qiáng)引用
    • 程序代碼中普遍存在的,例如Object obj=new Object(),只要強(qiáng)引用存在,永遠(yuǎn)不會被回收
  • 軟引用
    • 有用但并非必需的 。在內(nèi)存溢出之前,會對這些對象列進(jìn)回收范圍進(jìn)行二次回收。如果回收后還沒有內(nèi)存,就會拋出內(nèi)存溢出異常。
  • 弱引用
    • 當(dāng)垃圾收集工作時,不管內(nèi)存是否足夠,都會被回收。
  • 虛引用
    • 為一個對象設(shè)置虛引用的唯一目的,就是在回收時會得到一個通知。
生存還是死亡?
  • 如果對象在進(jìn)行可達(dá)性分析之后發(fā)現(xiàn)沒有GC root相連,那他將會進(jìn)行一次標(biāo)記,并進(jìn)行一次篩選,篩選的條件是有沒有必要執(zhí)行finalize()方法。
    • 當(dāng)對象沒有覆蓋finalize()方法,或者虛擬機(jī)已經(jīng)執(zhí)行過finalize(),則判定為不執(zhí)行。
    • 如果判定為執(zhí)行,會將對象放入一個F-Queue中,稍后去執(zhí)行。執(zhí)行時會進(jìn)行二次標(biāo)記,這個時候如果與引用鏈建立關(guān)聯(lián),就可以拯救自己了~~~~~~
回收方法區(qū)
  • 回收效率低,而且回收條件非??量?。
  • 主要回收廢棄常量和無用的類。

垃圾收集算法

標(biāo)記-清除(Mark-Sweep)

  • 首先標(biāo)記出需要回收的對象,標(biāo)記完成后統(tǒng)一回收
  • 缺點(diǎn):
    • 效率不高:標(biāo)記和清除效率都不高
    • 空間問題:會導(dǎo)致大量的內(nèi)存碎片 程序需要分配較大對象時,無法找到連續(xù)的內(nèi)存,不得不提前再進(jìn)行內(nèi)存回收。


      標(biāo)記-清除

復(fù)制

  • 將內(nèi)存氛圍兩份,每次只使用一份,當(dāng)這一塊用完了,就將還存活的對象復(fù)制到另一塊上,然后再把這一塊內(nèi)存清空。
  • 好處
    • 不會產(chǎn)生內(nèi)存碎片,只需移動堆頂指針,順序分配,操作簡單,運(yùn)行高效。
  • 缺點(diǎn)
    • 將內(nèi)存縮小一半,代價太大。


      image
  • 應(yīng)用中 并不是按照1:1的比例劃分內(nèi)存。而是把eden和survivor按照8:1分配?;厥諘r,將eden和from sur中存活的對象一次性的放到to sur中。當(dāng)survivor內(nèi)存不夠時,需要old gen進(jìn)行分配擔(dān)保。這些對象將直接進(jìn)入老年代區(qū)域。(==詳細(xì)的參考下面的內(nèi)存分配與回收==)

小問題,為啥要有兩個survivor?

當(dāng)你把eden和from sur復(fù)制到to sur中后,清除eden和from ?,F(xiàn)在只有to中有數(shù)據(jù)了。
to中有數(shù)據(jù) 怎么做下一次的minor gc呢? 所以 要把to中的數(shù)據(jù) 再復(fù)制到from中?。。。?/p>

標(biāo)記-整理

  • 復(fù)制算法在對象存活率較高的情況的下,需要進(jìn)行大量的復(fù)制,效率不高。而且還需要額外的空間進(jìn)行擔(dān)保,所以老年代不用這種算法。
  • 算法和 標(biāo)記-清除差不多,只不過是在標(biāo)記之后不是對回收對象進(jìn)行清除,而是讓存活對象向一端移動,然后直接清理掉端邊界以外的內(nèi)存。


    image

分代收集

  • 根據(jù)對象存活周期的不同講內(nèi)存分為幾塊。
  • 一般java堆分為新生代和老生代
    • 新生代大批對象死去,少量存活,就采用 復(fù)制算法。
    • 老生代存活率高 就用標(biāo)記-清除 或者標(biāo)記-整理。

內(nèi)存分配與回收

image
  1. 對象優(yōu)先再Eden區(qū)分配
    • 當(dāng)eden沒有足夠的內(nèi)存分配時,虛擬機(jī)講發(fā)起一次Minor GC
  2. 大對象直接進(jìn)入老年代
    • 最典型的大對象就是那種很長的字符串或數(shù)組。
    • 虛擬機(jī)提供參數(shù) -Xx:PretenureSizeThreshold 大于這個設(shè)置值的直接進(jìn)入老年代
    • 目的:是為了避免eden和survivor之間發(fā)生大量的內(nèi)存復(fù)制
  3. 長期存活的對象直接進(jìn)入老年代
    • 虛擬機(jī)給每個對象定義了一個對象年齡計算器
    • 如果這個對象在eden出生,并經(jīng)過一次minor gc扔存活,并且能夠被survivor容納,年齡+1
    • 對象在survivor中熬過一次minor gc,年齡+1
    • 當(dāng)?shù)竭_(dá)默認(rèn)值(15),就晉升老年代??梢酝ㄟ^-Xx:MaxTenuringThreshold設(shè)置。
  4. 動態(tài)對象年齡判定
    • 如果在survivor中相同年齡所有對象大小的總和大于survivor內(nèi)存的一半,年齡大于或等于該年齡的對象直接晉升老年代,不用非要限制上面那個參數(shù)設(shè)定的年齡。
  5. 空間分配擔(dān)保
    • 只要老年代的連續(xù)內(nèi)存空間>新生代對象總大小 或者 歷次晉升的平均大小就會進(jìn)行minor gc,否則進(jìn)行full gc。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 806評論 0 1
  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,157評論 2 31
  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,818評論 11 349
  • 上一篇所用的代碼,是廖雪峰大大 python 數(shù)據(jù)庫章節(jié)中的 SQLAlchemy 中的部分。因為踩到不少坑,所以...
    旦暮何枯閱讀 184評論 0 0
  • 當(dāng)功率計成為自行車愛好者們都可以負(fù)擔(dān)的配件后,這種裝備越來越受到人們的歡迎。那功率計是否可以應(yīng)用于跑步呢?Stry...
    晃悠的老劉忙閱讀 1,441評論 0 1

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