java虛擬機(jī)在執(zhí)行java程序過程中會把它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域,按照《java虛擬機(jī)規(guī)范》的規(guī)定驶忌,主要包含以下幾個運行時數(shù)據(jù)區(qū)域:
程序計數(shù)器
當(dāng)前線程所執(zhí)行字節(jié)碼的行號指示器厉膀,字節(jié)碼解析器工作時通過改變該計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,每條線程都需要一個獨立的程序計數(shù)器,線程私有內(nèi)存醋界。
java虛擬機(jī)棧
虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀竟宋,用于存儲局部變量表,操作數(shù)棧形纺、動態(tài)鏈接丘侠、方法出口等信息。
每一個方法被調(diào)用到執(zhí)行完成的過程逐样,對應(yīng)一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程蜗字,該區(qū)域同樣是每個線程私有。
局部變量存放了編譯器可知的基礎(chǔ)類型和對象引用脂新,局部變量表需要的內(nèi)存空間在編譯期間完成分配挪捕。
該區(qū)域規(guī)定了兩種異常:線程請求的棧深度大于虛擬機(jī)允許的深度,將拋出StackOverflowError異常戏羽;虛擬機(jī)動態(tài)擴(kuò)展無法申請足夠的內(nèi)存時拋出OutOfMemoryError異常担神。
本地方法棧
虛擬機(jī)棧為jvm執(zhí)行的java方法(字節(jié)碼)提供服務(wù),本地方法棧為虛擬機(jī)使用的Native方法服務(wù)始花。
java堆
java堆被所有線程共享的一塊區(qū)域妄讯,在虛擬機(jī)啟動時候創(chuàng)建,此內(nèi)存區(qū)域的唯一目的就是存儲對象的實例酷宵。從垃圾收集的標(biāo)準(zhǔn)對堆細(xì)分:新生代和老年代(可以進(jìn)一步細(xì)分后續(xù)說明)亥贸。當(dāng)堆中有實例沒有分配內(nèi)存,同時堆無法申請內(nèi)存會出現(xiàn)OutOfMemoryError異常浇垦。
方法區(qū)
與java堆一樣炕置,被各個線程所共享,用于存儲已經(jīng)被虛擬機(jī)加載的類信息男韧、常量朴摊、靜態(tài)變量、編譯后代碼等數(shù)據(jù)此虑。該區(qū)域很少出現(xiàn)垃圾回收的行為甚纲,或者可以選擇不實現(xiàn)。當(dāng)然朦前,該區(qū)域回收主要針對常量池和類型的卸載介杆。
運行時常量池
運行時常量池是方法區(qū)的一部分。Class文件中有一項信息是常量池韭寸,用于存放編譯期生成的各種字面量和符號引用春哨,該部分內(nèi)容在類加載后存放到方法區(qū)的運行時常量池中。
直接內(nèi)存
直接內(nèi)存不是jvm運行時數(shù)據(jù)區(qū)的一部分恩伺,也不是jvm規(guī)范的定義的內(nèi)存區(qū)域赴背。常見使用在NIO中,可以使用Native函數(shù)直接分配堆外內(nèi)存,然后通過DirectByteBuffer對象直接對該塊內(nèi)存引用操作癞尚。所以該區(qū)域大小受制于本機(jī)物理內(nèi)存的大小耸三。
其中乱陡,程序計數(shù)器浇揩、虛擬機(jī)棧和本地方法棧為線程私有,而java堆憨颠、方法區(qū)以及運行時常量為線程共有胳徽。
堆內(nèi)存具體分配
JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64爽彤;JVM最大分配的內(nèi)存由-Xmx指 定养盗,默認(rèn)是物理內(nèi)存的1/4。
默認(rèn)空余堆內(nèi)存小于40%時适篙,JVM就會增大堆直到-Xmx的最大限制往核;空余堆內(nèi)存大于70%時,JVM會減少堆直到 -Xms的最小限制嚷节。
因此服務(wù)器一般設(shè)置-Xms聂儒、-Xmx相等以避免在每次GC 后調(diào)整堆的大小。
如下圖所示硫痰,java堆內(nèi)存包括以下幾個部分:
- Young Generation:年輕代包含圖中的Eden+From+To三部分構(gòu)成衩婚,Eden Space用于存放新創(chuàng)建的對象,F(xiàn)rom區(qū)和To區(qū)都是救助空間Survivor Space效斑。當(dāng) Eden 區(qū)滿了非春,會觸發(fā)Minor GC。
- Tenured:老年代用于存放長壽的對象缓屠,在年輕帶中經(jīng)歷了N次垃圾回收后仍然存活的對象奇昙,就會被放到Old區(qū)中。
- Perm:永久代主要保存class,method,filed對象敌完,這部門的空間一般不會溢出储耐,除非一次性加載了很多的類。
內(nèi)存申請
JVM內(nèi)存申請主要包括以下流程:
- 對于新對象jvm首先向Eden申請空間蠢挡,如果Enden空間充足則結(jié)束弧岳,否則進(jìn)入下一步;
- JVM 試圖釋放在Eden中所有不活躍的對象(MinorGC)业踏,釋放后若Eden空間仍然不足以放入新對象禽炬,則試圖將部分Eden中活躍對象放入Survivor區(qū)即上圖中的(From+To區(qū)域)
- Survivor區(qū)作為Eden區(qū)和Old區(qū)的緩沖,在Survivor區(qū)的對象經(jīng)歷若干次收集仍然存活的勤家,如果Old區(qū)空間充足就會被轉(zhuǎn)移到年老區(qū)腹尖。
- 當(dāng)Old區(qū)空間不夠時,JVM 會觸發(fā)FullGC對Old區(qū)域進(jìn)行垃圾回收
- 經(jīng)過上述FullGC整理后伐脖,若Survivor及OLD區(qū)仍然無法存放從Eden復(fù)制過來的部分對象热幔,則會出現(xiàn)“out of memory”報錯
內(nèi)存泄露和內(nèi)存溢出
1.內(nèi)存泄露
指程序中動態(tài)分配內(nèi)存給一些臨時對象乐设,但是對象不會被GC所回收,它始終占用內(nèi)存绎巨。
常見場景:
- 長生命周期的對象持有短生命周期對象的引用(例如:在全局靜態(tài)map中緩存局部變量近尚,且沒有清空操作,隨著時間的推移场勤,這個map會越來越大戈锻,造成內(nèi)存泄露)
- 修改hashset中對象的參數(shù)值,且參數(shù)是計算哈希值的字段(無法找到該對象)
- 機(jī)器的連接數(shù)和關(guān)閉時間設(shè)置(長時間開啟非常耗費資源的連接)
2.內(nèi)存溢出(OutOfMemoryError(簡稱OOM))
內(nèi)存溢出就是要求分配的內(nèi)存超出了系統(tǒng)能給你的和媳,系統(tǒng)不能滿足需求芒篷,于是產(chǎn)生溢出都许。
參考文檔
《深入理解java虛擬機(jī)》——周志明
http://blog.csdn.net/ithomer/article/details/6252552