【JVM之運(yùn)行時(shí)數(shù)據(jù)區(qū)2】堆

一俯萌、堆的概述

JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)如下:


在這里插入圖片描述

一個(gè)Java程序運(yùn)行起來對(duì)應(yīng)著一個(gè)進(jìn)程(操作系統(tǒng)的進(jìn)程),一個(gè)進(jìn)程對(duì)應(yīng)著一個(gè)JVM實(shí)例。而一個(gè)JVM實(shí)例就對(duì)應(yīng)著一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)雏掠,則其中就包含著一個(gè)堆空間寄锐。一個(gè)進(jìn)程中的多個(gè)線程共享這個(gè)堆空間兵多。
幾乎所有的對(duì)象實(shí)例和數(shù)組都在堆上分配內(nèi)存(并非所有的對(duì)象都是在堆上分配內(nèi)存,后面會(huì)提到這一特殊情況)橄仆,即堆空間存儲(chǔ)著絕大部分的對(duì)象剩膘。虛擬機(jī)棧中的棧幀保存著數(shù)組或者對(duì)象的引用,這個(gè)引用就指向?qū)ο蠡蛘邤?shù)組在堆中的位置盆顾。

有代碼如下:

package com.fengjian.www.RunDataArea.Heap;

public class simpleHeap {
    
    private int id;

    public simpleHeap(int id){
        this.id = id;
    }

    public void show(){
        System.out.println("my id is:"+id);
    }

    public static void main(String[] args) {
        simpleHeap s1 = new simpleHeap(1);
        simpleHeap s2 = new simpleHeap(2);
    }
}

代碼對(duì)應(yīng)下圖怠褐,

在這里插入圖片描述

二、堆內(nèi)存的細(xì)分

堆您宪,是垃圾回收(GC奈懒,Garbage Collection,垃圾收集)的重點(diǎn)區(qū)域宪巨。我們常說的虛擬機(jī)的GC磷杏,大部分都與堆有關(guān)。
在一個(gè)Java方法結(jié)束后(即在虛擬機(jī)棧中出棧后捏卓,再?zèng)]有引用指向堆中的對(duì)象)极祸,堆中的對(duì)象并不會(huì)馬上被移除,而是在GC時(shí)才會(huì)被移除天吓。
由于現(xiàn)代垃圾收集器大部分都基于分代收集理論設(shè)計(jì)贿肩,所以堆空間在邏輯上細(xì)分如下:

(1)、Java7及之前龄寞,分為三部分:新生代汰规、老年代、永久代
① 新生代: Yonug/New Generation Space

其中物邑,新生代劃分為一個(gè)Eden區(qū)(伊甸園區(qū))和兩個(gè)Survivor區(qū)(From Survivor和To Survivor)

②老年代:Tenure/Old Generation Space

③永久代:Perm Space

(2)溜哮、Java8及之后,也分為三部分:新生代色解、老年代茂嗓、元空間
Java8之后,取消了永久代科阎,改用元空間代替述吸,而新生代和老年代則繼續(xù)保留。
元空間:Mete Space
后面將詳細(xì)介紹永久代和元空間的區(qū)別。

值得注意的是蝌矛,雖然這里將永久代道批、元空間和新生代、老年代并列入撒,但實(shí)際上隆豹,永久代和元空間都不屬于堆空間的一部分。事實(shí)上茅逮,永久代和元空間是方法區(qū)的實(shí)現(xiàn)方式璃赡,即用永久代或者元空間來實(shí)現(xiàn)方法區(qū)
堆空間分出了一部分內(nèi)存給永久代來實(shí)現(xiàn)方法區(qū),而元空間則脫離了虛擬機(jī)內(nèi)存献雅,直接使用計(jì)算機(jī)的本地內(nèi)存作為自己的空間碉考。所以,我們常說的堆空間惩琉,本質(zhì)上只包含新生代和老年代的空間豆励。

示意圖如下,


在這里插入圖片描述

在這里插入圖片描述

在我們常見的表述中瞒渠,新生代、年輕代和新生區(qū)意思相同技扼,老年代伍玖、老年區(qū)和養(yǎng)老代也都是一個(gè)意思,只是叫法不同而已剿吻。

1窍箍、永久代簡(jiǎn)述

我們現(xiàn)在開發(fā)常用的SunJDK和OpenJDK都是采用的HotSpot虛擬機(jī)。而永久代就獨(dú)屬于HotSpot虛擬機(jī)獨(dú)有的丽旅,其他虛擬機(jī)則并沒有椰棘。
HotSpot虛擬機(jī)采用了永久代來實(shí)現(xiàn)方法區(qū)。
方法區(qū)是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一塊榄笙,是虛擬機(jī)規(guī)范中規(guī)定要有的一塊區(qū)域邪狞。但是對(duì)于如何實(shí)現(xiàn)這塊區(qū)域,虛擬機(jī)規(guī)范并沒有給出茅撞。所以各個(gè)虛擬機(jī)廠商可以自主的實(shí)現(xiàn)這一區(qū)域帆卓。即方法區(qū)是一個(gè)規(guī)范,永久代是HotSpot虛擬機(jī)實(shí)現(xiàn)方法區(qū)的一種方式米丘,后又用元空間來代替元空間剑令。

使用永久代的好處有:
①由于永久代和新生代、老年代是連續(xù)的物理內(nèi)存區(qū)域拄查,故HotSpot虛擬機(jī)的垃圾回收器可以像管理堆一樣管理方法區(qū)吁津,省去了專門為方法區(qū)設(shè)置垃圾回收的工作。
②永久代和老年代的垃圾回收是綁定的堕扶,一旦其中一個(gè)區(qū)域被占滿碍脏,這兩個(gè)區(qū)域都會(huì)進(jìn)行垃圾回收癣疟。

永久代的缺點(diǎn)也很明顯:
①在Java7之前,HotSpot虛擬機(jī)將納入字符串常量池的字符串(占用內(nèi)存較大)存儲(chǔ)在永久代中潮酒,導(dǎo)致了一系列的性能問題和內(nèi)存溢出錯(cuò)誤睛挚。(在Java7時(shí),已經(jīng)字符串常量池放到了堆中)
②永久代的大小指定困難急黎,太小容易出現(xiàn)永久代溢出扎狱,太大容易導(dǎo)致老年代溢出。(可用:java-XX:PermSizejava-XX:Perm設(shè)置)

由于永久代容易導(dǎo)致javaOutofMemoryError,因?yàn)橥ǔJ褂肞ermSize和MaxPermSize設(shè)置永久代的大小就決定了永久代的上限勃教,但是不是總能知道應(yīng)該設(shè)置為多大合適, 如果使用默認(rèn)值很容易遇到OOM錯(cuò)誤淤击。故決定用元空間的設(shè)置來取代永久代。
當(dāng)然故源,其實(shí)還有一個(gè)重要的原因就是污抬,要合并HotSpot虛擬機(jī)和JRockit虛擬機(jī)的代碼,JRockit沒有永久代的設(shè)計(jì)绳军,但是運(yùn)行良好印机,HotSpot虛擬機(jī)吸收了它的長(zhǎng)處,取消了永久代门驾。

2射赛、元空間簡(jiǎn)述

元空間使用的不是虛擬機(jī)所管理的內(nèi)存大小,而是使用的計(jì)算機(jī)本身的本地內(nèi)存奶是。因此其大小受限制于本地內(nèi)存楣责,但也可以通過以下參數(shù)設(shè)置大小:java-XX:MetaSpaceSize聂沙。如果沒有設(shè)置秆麸,則默認(rèn)根據(jù)運(yùn)行的Java程序動(dòng)態(tài)調(diào)整大小。
由于元空間的最大可分配空間是操作系統(tǒng)的可用內(nèi)存空間及汉,故我們不會(huì)遇到永久代存在的內(nèi)存溢出問題(OOM)沮趣。

三、老年代和新生代

存儲(chǔ)在JVM中的對(duì)象可以被分為2類:

  • 一類是生命周期較短的瞬時(shí)對(duì)象豁生,其創(chuàng)建和消亡都非惩枚荆快。
  • 另一類是對(duì)象的生命周期卻非常長(zhǎng)甸箱,有時(shí)甚至與JVM中的生命周期保持一致育叁。

Java堆區(qū)進(jìn)一步分為新生代和老年代。新生代又細(xì)分為Eden區(qū)和Survivor0區(qū)和Survivor1區(qū)芍殖。

在這里插入圖片描述

默認(rèn)情況下豪嗽,新生代與老年代的占比是1:2,即新生代占1/3,。
也可以通過以下參數(shù)調(diào)整:

-XX:NewRatio = 1龟梦,即老年代占1/2
-XX:NewRatio = 2隐锭,默認(rèn)情況,即老年代占2/3
-XX:NewRatio = 3计贰,即老年代占1/4
...

當(dāng)然钦睡,大部分情況下是不會(huì)進(jìn)行調(diào)整的。
在HotSpot虛擬機(jī)中躁倒,Eden空間和兩個(gè)Survivor區(qū)的所占比例是8:1:1荞怒。
也可以通過以下參數(shù)調(diào)整:

-XX:SurvivorRatio = 8,默認(rèn)情況秧秉,即Eden區(qū)占8/10褐桌。(兩個(gè)Survivor區(qū)大小一致)

幾乎所有的Java對(duì)象都是在Eden區(qū)New出來的,而絕大部分的Java對(duì)象的銷毀都在新生代進(jìn)行象迎。
有研究表明荧嵌,新生代80%的對(duì)象都是“朝生夕死”的。

四砾淌、對(duì)象分配內(nèi)存的過程

一個(gè)對(duì)象分配內(nèi)存的過程簡(jiǎn)述如下:
(1)啦撮、new的新對(duì)象放在Eden區(qū),此區(qū)有大小限制拇舀。
(2)逻族、當(dāng)Eden區(qū)的空間被填滿時(shí),程序又要?jiǎng)?chuàng)建對(duì)象骄崩,此時(shí)就應(yīng)進(jìn)行GC以回收內(nèi)存。JVM的垃圾回收器將對(duì)Eden區(qū)進(jìn)行垃圾回收(該GC被稱為MinorGC)薄辅,將Eden區(qū)中不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀要拂,再加載新的對(duì)象到Eden區(qū)。
(3)站楚、進(jìn)行GC后脱惰,將Eden區(qū)中的剩余對(duì)象移動(dòng)至Survivor0區(qū)。
(4)窿春、如果Eden區(qū)再次被填滿拉一,則繼續(xù)觸發(fā)MinorGC,GC的范圍包括Survivor區(qū)旧乞。GC后蔚润,此時(shí)Eden區(qū)剩余的對(duì)象,以及上次GC后被移動(dòng)至Survivor0區(qū)并且在本次GC中沒有被回收的剩余對(duì)象尺栖,都會(huì)一起被移動(dòng)到Survivor1區(qū)嫡纠。
(5)、如果再次經(jīng)歷GC,Eden區(qū)和Survivor1區(qū)的剩余對(duì)象將繼續(xù)移動(dòng)到Survivor0區(qū)除盏。再次GC后叉橱,則一起去Survivor1區(qū),如此反復(fù)者蠕。

那么窃祝,一個(gè)對(duì)象什么時(shí)候會(huì)去到老年代呢?
在新生代(包含Eden區(qū)和兩個(gè)Survivor區(qū))中踱侣,一個(gè)對(duì)象如果經(jīng)歷了一次GC后(一般是MinorGC)粪小,其年齡值就會(huì)加一。當(dāng)一個(gè)對(duì)象經(jīng)歷了15次GC后泻仙,即年齡值達(dá)到15糕再,就會(huì)晉升(Promotion)到老年代。這個(gè)年齡到達(dá)一定值后晉升的值玉转,被稱為閾值突想。閾值可以通過如下參數(shù)調(diào)整:

-XX:MaxTenuringThreshold = 15,默認(rèn)情況下究抓,閾值設(shè)置為15猾担。

這是一個(gè)對(duì)象從新生代晉升到老年代的一般情況,當(dāng)然也會(huì)有一些特殊情況刺下,使對(duì)象的年齡值沒有達(dá)到閾值也會(huì)晉升到老年代绑嘹。

(6)、在老年代橘茉,若老年代內(nèi)存不足時(shí)工腋,則會(huì)觸發(fā)GC(一般是MajorGC或者FullGC,兩者區(qū)別后面介紹)畅卓,并進(jìn)行垃圾回收擅腰。
(7)、如果老年代在進(jìn)行GC后依然無法進(jìn)行對(duì)象的存儲(chǔ)翁潘,那么就會(huì)產(chǎn)生OOM異常趁冈。

這就是對(duì)象分配的一般過程,由于文字描述不易理解拜马,下面將進(jìn)行圖解渗勘。

過程1:

在這里插入圖片描述

解析1:
①當(dāng)對(duì)象創(chuàng)建時(shí),會(huì)先放在Eden區(qū)俩莽。
②當(dāng)Eden區(qū)滿后旺坠,觸發(fā)MinorGC,并進(jìn)行GC豹绪。
③MinorGC的機(jī)制可判斷沒有對(duì)象引用的對(duì)象為垃圾价淌,并將其回收申眼。
④GC后剩余的對(duì)象將被移自Survivor0區(qū),此時(shí)該區(qū)被稱為from區(qū)蝉衣。
⑤而沒有存放對(duì)象的Survivor1區(qū)則稱為to區(qū)括尸,to區(qū)總是空的。
⑥to區(qū)和from區(qū)是不固定的病毡,誰空誰就是to區(qū)濒翻。
⑦每經(jīng)歷一次GC,剩余對(duì)象的年齡值加一。

過程2:

在這里插入圖片描述

解析2:
①當(dāng)Eden區(qū)又一次滿后啦膜,繼續(xù)觸發(fā)MinorGC有送,并進(jìn)行GC。
②GC的范圍是整個(gè)新生代僧家,所以除了Eden區(qū)的垃圾對(duì)象會(huì)被回收外雀摘,Survivor0區(qū)的垃圾對(duì)象也會(huì)被回收。
③經(jīng)過GC后八拱,所有剩余對(duì)象進(jìn)入Survivor1區(qū)阵赠,并且年齡值均加一。

過程3:

在這里插入圖片描述

解析3:

①若Eden區(qū)再滿后肌稻,繼續(xù)觸發(fā)MinorGC清蚀,并進(jìn)行GC。
②經(jīng)過GC后爹谭,所有剩余對(duì)象又進(jìn)入Survivor0區(qū)枷邪,并且年齡值均加一。
③新生代中的對(duì)象就是如此經(jīng)歷GC和反復(fù)移動(dòng)诺凡。

過程4:

在這里插入圖片描述

解析4:

①當(dāng)有剩余對(duì)象經(jīng)歷了閾值規(guī)定的GC次數(shù)后(默認(rèn)是15次)东揣,就會(huì)發(fā)生晉升(promotion),從新生代晉升至老年代腹泌。
②老年代中的對(duì)象生命周期一般比較長(zhǎng)救斑,但其內(nèi)存空間也是有限的,故在其內(nèi)存空間滿后也會(huì)進(jìn)行GC真屯。
③老年代常見的GC有MajorGC和FullGC。
④如果老年代中執(zhí)行GC后穷娱,依然無法滿足存儲(chǔ)對(duì)象的需求绑蔫,則會(huì)報(bào)OOM,

上訴分配過程就是對(duì)象的一般分配過程泵额,但對(duì)象不一定都是經(jīng)歷了一定GC次數(shù)后才會(huì)晉升到老年代的配深。

下面就用具體的流程圖來說明:


在這里插入圖片描述

特殊情況在于:

①當(dāng)新對(duì)象過大,而Eden區(qū)放置不下時(shí)嫁盲,會(huì)直接將該大對(duì)象晉升到老年代篓叶。如果老年代放不下烈掠,則會(huì)觸發(fā)FullGC,GC后若再放不下缸托,則會(huì)報(bào)OOM左敌。
②當(dāng)Eden區(qū)中的對(duì)象經(jīng)過MinorGC后,Survivor區(qū)(S0或S1)放不下該對(duì)象俐镐,由于Survivor區(qū)滿不會(huì)觸發(fā)GC故該對(duì)象也會(huì)直接晉升到老年代中矫限。

五、幾個(gè)常見GC的概述

前面講到幾個(gè)GC:MinorGC佩抹、MajorGC叼风、FullGC,現(xiàn)在來簡(jiǎn)單概述它們的區(qū)別棍苹。

JVM在進(jìn)行GC時(shí)无宿,并非每次都會(huì)對(duì)新生代,老年代枢里,元空間(方法區(qū))這三個(gè)內(nèi)存區(qū)域一起回收孽鸡,大部分回收都是發(fā)生在新生代。

對(duì)于Hotspot虛擬機(jī)坡垫,其GC按照回收區(qū)域又分為兩大類型:部分收集(Partial GC)和整堆收集(Full GC)梭灿。

  • 部分收集:不是完整收集整個(gè)Java堆空間。其中又有:

①新生代收集(MinorGC):即針對(duì)新生代(Eden區(qū)冰悠,兩個(gè)Survivor區(qū))的垃圾收集堡妒。
②老年代收集(MajorGC):即針對(duì)老年代的垃圾收集。但目前只有CMS GC這款垃圾收集器在進(jìn)行MajorGC時(shí)溉卓,只單獨(dú)收集老年代的垃圾皮迟。

  • 整堆收集:收集整個(gè)Java堆(包括老年代,新生代)和方法區(qū)的垃圾桑寨。

1伏尼、新生代GC(MinorGC)

①當(dāng)Eden區(qū)空間不足時(shí),就會(huì)觸發(fā)MinorGC尉尾,但Survivor區(qū)不足則不會(huì)觸發(fā)爆阶。MinorGC的范圍是整個(gè)新生代。
②由于大多數(shù)Java對(duì)象的創(chuàng)建和消亡都比較快沙咏,故MinorGC非常頻繁辨图,一般回收速度也比較快。
③MinorGC會(huì)引發(fā)STW(stop the word)肢藐,即暫停用戶線程故河,也就是我們平時(shí)執(zhí)行我們編寫的代碼的線程,直到垃圾回收結(jié)束吆豹,用戶線程才會(huì)恢復(fù)鱼的。

2理盆、老年代GC(MajorGC/FullGC)

①老年代中的GC,一般為MajorGC或FullGC。
②在老年代空間不足時(shí)凑阶,會(huì)先嘗試發(fā)起MinorGC猿规,在此之后空間仍然不足,才會(huì)觸發(fā)MajorGC(但并不絕對(duì))
③MajorGC的速度會(huì)比MinorGC慢10倍以上晌砾,STW時(shí)間更長(zhǎng)

3坎拐、整堆收集(FullGC)

①調(diào)用system.gc()時(shí),系統(tǒng)會(huì)建議執(zhí)行FullGC,但并不是一定會(huì)執(zhí)行养匈。
②在老年代空間不足時(shí)哼勇,可能會(huì)觸發(fā)FullGC。
③方法區(qū)空間不足時(shí)呕乎,也會(huì)觸發(fā)FullGC积担。
④FullGC是開發(fā)中應(yīng)盡量避免的,因?yàn)槠鋾和S脩艟€程的時(shí)間會(huì)很長(zhǎng)猬仁。

六帝璧、內(nèi)存分配策略

我們知道,如果對(duì)象在Eden區(qū)出生并經(jīng)歷第一次MinorGC后仍然存活湿刽,并且能夠被Survivor區(qū)容納的話的烁,將會(huì)被移動(dòng)到Survivor區(qū)中,并將年齡值加1诈闺。對(duì)象每經(jīng)歷一次GC渴庆,其年齡值加一。而針對(duì)不同年齡值得對(duì)象雅镊,其內(nèi)存分配策略如下:

(1)襟雷、對(duì)象優(yōu)先在Eden區(qū)分配。
(2)仁烹、大對(duì)象直接分配在老年代耸弄。

①默認(rèn)情況下谷异,超過Eden區(qū)大小的大對(duì)象直接分配到老年代舌涨,比如 new byte[40010241024]這個(gè)400MB大小的字節(jié)數(shù)組就是一個(gè)大對(duì)象。
②可以通過參數(shù)設(shè)置多大的對(duì)象直接在老年代分配:-XX:PretenureSizeThreshold
③要避免程序中出現(xiàn)這樣的大對(duì)象雨席,尤其是消亡得很快的大對(duì)象征唬。因?yàn)榇髮?duì)象需要連續(xù)的存儲(chǔ)空間才能存放它們震叮,這就意味著即使內(nèi)存中還有不少空間,但為了能有足夠的連續(xù)空間鳍鸵,也不得不進(jìn)行GC,進(jìn)而導(dǎo)致用戶線程暫停尉间,降低性能偿乖。

(3)击罪、長(zhǎng)期存活的對(duì)象進(jìn)入老年代。
(4)贪薪、動(dòng)態(tài)對(duì)象年齡判斷:為了更好地適應(yīng)不同年齡狀況媳禁,HotSpot虛擬機(jī)并不是一定要求對(duì)象必須要達(dá)到閾值才能晉升老年代。如果Survivor區(qū)中所有相同年齡對(duì)象的大小的總數(shù)大于Survivor區(qū)的一半画切,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代了竣稽。
(5)、空間分配擔(dān)保:在新生代進(jìn)行MinorGC前霍弹,虛擬機(jī)會(huì)
①先檢查老年代中的連續(xù)空間是否滿足新生代中所有對(duì)象的大小毫别。
②若滿足,則本次MinorGC是安全的典格。
③若不滿足岛宦,則會(huì)查看-XX:HandlePromotionFailure的設(shè)置
④若-XX:HandlePromotionFailure = true,即為允許擔(dān)保失敗耍缴,于是就去檢查老年代中的連續(xù)空間是否大于新生代中以前晉升到老年代的對(duì)象的平均大小砾肺。
⑤如果老年代中的連續(xù)空間大于以前晉升到老年代的對(duì)象的平均大小,則繼續(xù)進(jìn)行MinorGC(但本次MinorGC不一定是安全的)
⑥如果連續(xù)空間小于平均大小防嗡,或者參數(shù)設(shè)置為-XX:HandlePromotionFailure = false(即不允許冒險(xiǎn))变汪,則將MinorGC改為FullGC。

七蚁趁、堆空間分代思想

為什么要把Java堆分代裙盾,不分代是否可行?

其實(shí)不分代也是完全可以的荣德,分代的原因就是優(yōu)化GC的性能闷煤。
如果沒有分代,那么所有的對(duì)象都在一塊涮瞻,GC時(shí)需要找那些對(duì)象沒用鲤拿,就需要掃描整個(gè)Java堆,比較耗費(fèi)性能署咽。而其實(shí)很多對(duì)象都是消亡得比較快的近顷,如果采用分代將新創(chuàng)建的對(duì)象放到某一個(gè)地方,當(dāng)GC時(shí)可以先對(duì)這塊放置新對(duì)象的區(qū)域進(jìn)行回收宁否,則可以騰出很大空間窒升,同時(shí)也提高了GC的效率。

八慕匠、本地線程分配緩沖TLAB

TLAB(Thread Local Allocation Buffer)出現(xiàn)在為對(duì)象分配內(nèi)存的過程中饱须。
堆區(qū)是線程共享的區(qū)域,任何線程都可以訪問堆區(qū)台谊。由于對(duì)象實(shí)例的創(chuàng)建在堆區(qū)中非常頻繁蓉媳,因此在并發(fā)環(huán)境下從堆區(qū)中劃分內(nèi)存空間是線程不安全的譬挚。
為了避免多個(gè)線程在堆區(qū)中操作同一個(gè)地址,需要使用加鎖等機(jī)制酪呻,但這也會(huì)影響到內(nèi)存的分配速度减宣。為了解決這一問題,提出了TLAB玩荠。

1漆腌、什么是TLAB

虛擬機(jī)為每個(gè)線程分配了一個(gè)線程私有的緩沖區(qū)域,它包含在Eden區(qū)中阶冈。哪個(gè)線程要分配內(nèi)存闷尿,就在哪個(gè)線程的本地緩沖區(qū)中分配,只有本地緩沖區(qū)用完了眼溶,分配新的緩沖區(qū)需要才同步加鎖悠砚。


在這里插入圖片描述

2、TLAB的特點(diǎn)

①可以通過參數(shù)-XX:UseTLAB來設(shè)置是否開啟TLAB空間(默認(rèn)開啟)堂飞。
②盡管不是所有的對(duì)象實(shí)例都能在TLAB中成功分配內(nèi)存灌旧,但虛擬機(jī)確實(shí)將TLAB作為內(nèi)存分配的首選。
③默認(rèn)情況下绰筛,TLAB空間非常小枢泰,僅占Eden空間的1%。

可通過:-XX:TLABWasteTargetPercent來設(shè)置TLAB空間的百分比大小铝噩。
④一旦對(duì)象在TLAB空間中分配內(nèi)存失敗衡蚂,則虛擬機(jī)會(huì)嘗試通過加鎖機(jī)制確保操作的原子性,從而直接在Eden區(qū)空間中分配內(nèi)存骏庸。

九毛甲、堆不是對(duì)象分配中的唯一選擇

1、逃逸分析

對(duì)象在堆中分配內(nèi)存具被,這是普遍的常識(shí)玻募。但也有一種特殊的情況,那就是通過逃逸分析后發(fā)現(xiàn)一姿,一個(gè)對(duì)象沒有逃逸出方法七咧,則就可能被優(yōu)化成棧上分配。即無需在堆上分配內(nèi)存叮叹,也無需進(jìn)行垃圾回收艾栋。
逃逸分析的基本行為就是分析對(duì)象動(dòng)態(tài)作用域,并決定是否在棧上分配蛉顽。

  • 當(dāng)一個(gè)對(duì)象在方法中被定義以后蝗砾,對(duì)象只在方法內(nèi)部使用,則認(rèn)為沒有發(fā)生逃逸。
  • 當(dāng)一個(gè)對(duì)象在方法中被定義以后遥诉,在外部方法中被引用拇泣,則認(rèn)為發(fā)生了逃逸。例如矮锈,作為調(diào)用參數(shù),傳遞到其他地方睁蕾。

沒有發(fā)生逃逸的對(duì)象苞笨,則可以分配到棧上,隨著方法執(zhí)行的結(jié)束子眶,占用的棧幀空間就被移除了瀑凝。

有代碼如下,

public void my_method{
    V v = new V();
    //use v
    //...
    v = null;
}

在my_method方法中臭杰,對(duì)象v是在方法內(nèi)部被創(chuàng)建的粤咪,并且在方法的最后,將v設(shè)置為null渴杆,即再無引用指向之前的對(duì)象寥枝,則意味著,該方法的作用空間只在my_method方法內(nèi)磁奖,所以可認(rèn)為該對(duì)象沒有發(fā)生逃逸囊拜,故可以該對(duì)象可分配到棧上。

有代碼如下比搭,

public static StringBuffer createString1(String str1,String str2){
        StringBuffer sb = new StringBuffer();
        sb.append(str1);
        sb.append(str2);
        return sb;
    }

createString1()方法中冠跷,由于返回了sb,即返回了對(duì)象的引用身诺。則若在其他方法中調(diào)用了createString1()蜜托,那么接受了createString1()方法返回值的變量,實(shí)際上就指向了new StringBuffer();這個(gè)對(duì)象霉赡,即可認(rèn)為該對(duì)象發(fā)生了逃逸橄务。

若想使該方法不發(fā)生逃逸,可這樣改寫:

public static String createString2(String str1,String str2){
        StringBuffer sb = new StringBuffer();
        sb.append(str1);
        sb.append(str2);
        return sb.toString();
    }

這樣子改寫同廉,把 StringBuilder 變量控制在了當(dāng)前方法之內(nèi)仪糖,沒有逃出當(dāng)前方法作用域,故可以進(jìn)行棧上分配迫肖,則當(dāng)方法結(jié)束時(shí)锅劝,new StringBuffer();這個(gè)對(duì)象就會(huì)被移除,不用再經(jīng)歷垃圾回收蟆湖,提升了性能故爵。而返回的將是一個(gè)新的字符串,其內(nèi)容與sb指向的對(duì)象一致。

關(guān)于逃逸分析的常見場(chǎng)景诬垂,有


/**
 * 逃逸分析
 *
 * 1劲室、如何快速分析是否發(fā)生了逃逸分析,就看new的對(duì)象實(shí)體是否有可能在方法外被調(diào)用结窘。
 */

public class EscapeAnalysis {

    public EscapeAnalysis obj;

    /**
     * 方法返回escapeAnalysis對(duì)象很洋,發(fā)生逃逸
     */
    public EscapeAnalysis getInstance(){
        return obj == null? new EscapeAnalysis() : obj;
    }

    /**
     * 為成員屬性賦值,發(fā)生逃逸
     * 即使obj對(duì)象修飾為static的隧枫,其仍然會(huì)發(fā)生逃逸
     */
    public void setObj(){
        this.obj = new EscapeAnalysis();
    }

    /**
     * 對(duì)象作用域僅在當(dāng)前方法中有效喉磁,沒有發(fā)生逃逸
     */
    public void useEscapeAnalysis(){
        EscapeAnalysis e = new EscapeAnalysis();
    }

    /**
     * 引用成員變量的值,發(fā)生逃逸
     * 因?yàn)閚ew的對(duì)象實(shí)體不是在本方法中
     */
    public void applyEscapeAnalysis(){
        EscapeAnalysis e = getInstance();
    }

}

3官脓、逃逸分析參數(shù)設(shè)置

  • Java7之后协怒,HotSpot默認(rèn)開啟逃逸分析
  • -XX:+DoEscapeAnalysis 顯式開啟逃逸分析
  • -XX:-DoEscapeAnalysis 關(guān)閉逃逸分析
  • -XX:-PrintEscapeAnalysis 查看逃逸分析的篩選結(jié)果

4、逃逸分析的作用:優(yōu)化

使用逃逸分析卑笨,編譯器可對(duì)代碼做如下優(yōu)化:
(1)孕暇、棧上分配。將堆分配轉(zhuǎn)化為棧上分配赤兴。如果一個(gè)對(duì)象在程序中被分配妖滔,而該指向該對(duì)象不會(huì)發(fā)生逃逸,那么該對(duì)象可能是棧分配搀缠,而不是堆分配铛楣。
(2)、同步省略艺普。如果一個(gè)對(duì)象唄發(fā)現(xiàn)只能從一個(gè)線程被訪問到簸州,那么可以對(duì)于這個(gè)對(duì)象的操作可以不考慮同步。
(3)歧譬、分離對(duì)象或者標(biāo)量替換岸浑。有的對(duì)象可能不需要作為一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問到,那么對(duì)象的部分(或全部)可以不存儲(chǔ)在內(nèi)存瑰步,而是存儲(chǔ)在CPU寄存器中矢洲。

得出一個(gè)結(jié)論:開發(fā)中能使用局部變量,就不要使用在方法外定義的變量缩焦。

5读虏、關(guān)于棧上分配

JIT編譯器在編譯期間根據(jù)逃逸分析的結(jié)果,發(fā)現(xiàn)如果一個(gè)對(duì)象并沒有逃逸出方法袁滥,則可能被優(yōu)化成棧上分配盖桥。分配完成后,繼續(xù)在調(diào)用棧內(nèi)執(zhí)行题翻,最后線程結(jié)束揩徊,棧空間被回收,局部變量對(duì)象也被回收塑荒。這樣就無需進(jìn)行垃圾回收了熄赡。
常見的棧上分配的場(chǎng)景是:
①給成員變量賦值
②方法返回值
③實(shí)例引用傳遞

6、關(guān)于同步省略

線程同步的代價(jià)是比較高的,同步的后果就是降低并發(fā)性和性能齿税,在動(dòng)態(tài)編譯同步的時(shí)候彼硫,JIT編譯器可以借助逃逸分析來判斷同步塊所使用的鎖對(duì)象是否只能夠被一個(gè)線程訪問到,而沒有被發(fā)布到其他線程凌箕。
如果沒有被其他線程訪問乌助,那么JIT編譯器在編譯這個(gè)同步塊的時(shí)候就會(huì)取消對(duì)這部分代碼的同步陌知。這樣就能大大提高并發(fā)性和性能。這個(gè)取消同步的過程就叫做同步省略掖肋,也叫鎖消除仆葡。

有代碼如下,

public void f(){
    Object hollis = new Object();
    synchronized(hollis ){
        System.out.println(hollis );
    }
}

代碼中對(duì)hollis這個(gè)對(duì)象進(jìn)行加鎖志笼,但是hollis 對(duì)象的生命周期只在f()方法中沿盅,并不會(huì)被其他線程所訪問到,所以在JIT編譯階段就會(huì)被優(yōu)化掉纫溃。
優(yōu)化成:

    public void f(){
        Object hollis = new Object();
        System.out.println(hollis );
    } 

7腰涧、 關(guān)于對(duì)象分離或標(biāo)量替換

標(biāo)量(Scalar)是指一個(gè)無法再分解成更小數(shù)據(jù)的數(shù)據(jù)。如Java中的原始數(shù)據(jù)類型就是標(biāo)量紊浩。
相對(duì)的窖铡,那么還可以分解的數(shù)據(jù)就叫聚合量(Aggregate),Java中的對(duì)象就是聚合量坊谁,因?yàn)樗梢苑纸獬善渌酆狭炕蛘邩?biāo)量费彼。
在JIT階段,如果經(jīng)過逃逸分析后發(fā)現(xiàn)口芍,一個(gè)對(duì)象不會(huì)被外界訪問的話箍铲,那么經(jīng)過JIT優(yōu)化,就會(huì)把這個(gè)對(duì)象拆解成若干個(gè)其中包含的成員變量來代替鬓椭。
這個(gè)過程就是標(biāo)量替換颠猴。
有代碼如下,


public static void main(String[] args) {
    alloc();
}

private static void alloc(){
    Point point = New Point(1,2);
}

class Point{
    private int x;
    private int y;
}

上述代碼就小染,經(jīng)過逃逸分析后發(fā)現(xiàn)翘瓮,對(duì)象point 沒有發(fā)生逃逸,則會(huì)進(jìn)行標(biāo)量替換氧映。經(jīng)過標(biāo)量替換后春畔,代碼被優(yōu)化成如下,


public static void main(String[] args) {
    alloc();
}

private static void alloc(){
    int x = 1;
    int y = 2;
}

可以看到,point這個(gè)對(duì)象聚合量經(jīng)過標(biāo)量替換后律姨,被替換成了兩個(gè)它包含的標(biāo)量xy振峻。
那么標(biāo)量替換有什么好處呢?
好處就是大大減少了堆內(nèi)存的占用择份,因?yàn)橐坏o需創(chuàng)建對(duì)象扣孟,就無需在堆上分配內(nèi)存了。標(biāo)量替換為棧上分配提供了很好的基礎(chǔ)荣赶。

標(biāo)量替換的JVM參數(shù)如下:

  • -XX:+EliminateAllocations 開啟標(biāo)量替換
  • -XX:-EliminateAllocations 關(guān)閉標(biāo)量替換
  • -XX:+PrintEliminateAllocations 顯示標(biāo)量替換詳情

8凤价、逃逸分析小結(jié)

目前,逃逸分析技術(shù)依然不成熟拔创,其根本原因是無法保證逃逸分析的性能消耗一定高過它的消耗利诺。雖然經(jīng)過逃逸分析后可以做到標(biāo)量替換、棧上分配和鎖消除剩燥,但是逃逸分析本身也需要經(jīng)歷一系列復(fù)雜的分析慢逾,這其實(shí)是一個(gè)相對(duì)耗時(shí)的過程。
比如灭红,如果經(jīng)過逃逸分析后侣滩,發(fā)現(xiàn)所有對(duì)象都是逃逸的,那逃逸分析的過程就白白浪費(fèi)了变擒。
雖然技術(shù)并不成熟君珠,但它還是即時(shí)編譯器優(yōu)化技術(shù)中一個(gè)重要的手段。

十娇斑、堆常見JVM參數(shù)小結(jié)

  • -XX:PrintfFlagsInitial 查看所有參數(shù)的默認(rèn)值
  • -XX:PrintfFlagsFinal 查看所有參數(shù)的z最終值
  • -Xms: 初始堆空間內(nèi)存(默認(rèn)為物理內(nèi)存的1/64)
  • -Xmx: 最大堆空間內(nèi)存(默認(rèn)為物理內(nèi)存的1/4)
  • -Xmn: 設(shè)置新生代的大胁咛怼(初始值及最大值)
  • -XX:NewRatio: 配置新生代和老年代在堆中結(jié)構(gòu)的占比
  • -XX:SurvivorRatio: 設(shè)置新生代中Eden區(qū)和S0/S1空間的比例
  • -XX:MaxTenuringThreshold 設(shè)置新生代中垃圾的最大年齡值
  • -XX: +PrintGCDetails: 輸出詳細(xì)的GC處理日志
  • 打印GC簡(jiǎn)要信息:①-XX: +PrintGC ② -verbose:gc
  • -XX:HandlePromotionFailure 是否設(shè)置空間分配擔(dān)保
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市悠菜,隨后出現(xiàn)的幾起案子舰攒,更是在濱河造成了極大的恐慌,老刑警劉巖悔醋,帶你破解...
    沈念sama閱讀 212,686評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摩窃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡芬骄,警方通過查閱死者的電腦和手機(jī)猾愿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來账阻,“玉大人蒂秘,你說我怎么就攤上這事√蕴” “怎么了姻僧?”我有些...
    開封第一講書人閱讀 158,160評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵规丽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我撇贺,道長(zhǎng)赌莺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評(píng)論 1 284
  • 正文 為了忘掉前任松嘶,我火速辦了婚禮艘狭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翠订。我一直安慰自己巢音,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,847評(píng)論 6 386
  • 文/花漫 我一把揭開白布尽超。 她就那樣靜靜地躺著官撼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪似谁。 梳的紋絲不亂的頭發(fā)上歧寺,一...
    開封第一講書人閱讀 50,043評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音棘脐,去河邊找鬼。 笑死龙致,一個(gè)胖子當(dāng)著我的面吹牛蛀缝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播目代,決...
    沈念sama閱讀 39,129評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼屈梁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了榛了?” 一聲冷哼從身側(cè)響起在讶,我...
    開封第一講書人閱讀 37,872評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霜大,沒想到半個(gè)月后构哺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡战坤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,645評(píng)論 2 327
  • 正文 我和宋清朗相戀三年曙强,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片途茫。...
    茶點(diǎn)故事閱讀 38,777評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碟嘴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囊卜,到底是詐尸還是另有隱情娜扇,我是刑警寧澤错沃,帶...
    沈念sama閱讀 34,470評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站雀瓢,受9級(jí)特大地震影響枢析,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜致燥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,126評(píng)論 3 317
  • 文/蒙蒙 一登疗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫌蚤,春花似錦辐益、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至箱蝠,卻和暖如春续捂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宦搬。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評(píng)論 1 267
  • 我被黑心中介騙來泰國打工牙瓢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人间校。 一個(gè)月前我還...
    沈念sama閱讀 46,589評(píng)論 2 362
  • 正文 我出身青樓矾克,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親憔足。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胁附,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,687評(píng)論 2 351