堆(Heap)
概述
●?一個(gè)JVM實(shí)例只存在一個(gè)堆內(nèi)存,堆也是Java內(nèi)存管理的核心區(qū)域。
●?Java堆區(qū)在JVM啟動(dòng)的時(shí)候即被創(chuàng)建,其空間大小也就確定了。是JVM管理的最大一塊內(nèi)存空間。
??堆內(nèi)存的大小是可以調(diào)節(jié)的。
●?《Java虛擬機(jī)規(guī)范》規(guī)定,堆可以處于物理.上不連續(xù)的內(nèi)存空間中,但在邏輯上它應(yīng)該被視為連續(xù)的。
●?所有的線程共享Java堆,在這里還可以劃分線程私有的緩沖區(qū)(Thread Local Allocation Buffer, TLAB) 。
●?《Java虛擬機(jī)規(guī)范》中對(duì)Java堆的描述是:所有的對(duì)象實(shí)例以及數(shù)組都應(yīng)當(dāng)在運(yùn)行時(shí)分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated )
??“幾乎”所有的對(duì)象實(shí)例都在這里分配內(nèi)存。一從實(shí)際使用角度看的。
●?數(shù)組和對(duì)象可能永遠(yuǎn)不會(huì)存儲(chǔ)在棧上,因?yàn)闂斜4嬉?,這個(gè)引用指向?qū)ο蠡蛘邤?shù)組在堆中的位置。
●?在方法結(jié)束后,堆中的對(duì)象不會(huì)馬上被移除,僅僅在垃圾收集的時(shí)候才會(huì)被移除。
●?堆,是GC ( Garbage Collection, 垃圾收集器)執(zhí)行垃圾回收的重點(diǎn)區(qū)域。
public class SimpleHeap {
private int id;
public SimpleHeap(int id) {
this.id = id;
}
public void show(){
System.out.println("My ID is" + this.id);
}
public static void main(String[] args) {
SimpleHeap s1 = new SimpleHeap(1);
SimpleHeap s2 = new SimpleHeap(2);
}
}

內(nèi)存細(xì)分
現(xiàn)代垃圾收集器大部分都基于分代收集理論設(shè)計(jì),堆空間細(xì)分為:


==JDK1.8之后,永久代取消了,由元空間取代==
設(shè)置堆空間大小只影響新生區(qū)和養(yǎng)老區(qū)
設(shè)置堆空間大小
●?Java堆區(qū) 用于存儲(chǔ)Java對(duì)象實(shí)例,那么堆的大小在JVM啟動(dòng)時(shí)就已經(jīng)設(shè)定好了,大家可以通過選項(xiàng)"- Xmx"和"-Xms"來進(jìn)行設(shè)置。
??"-Xms"用于表示堆區(qū)的起始內(nèi)存,等價(jià)于-XX: InitialHeapSize
??"-Xmx"則用于表示堆區(qū)的最大內(nèi)存,等價(jià)于-XX :MaxHeapSize
●?一旦堆區(qū)中的內(nèi)存大小超過“-Xmx”所指定的最大內(nèi)存時(shí),將會(huì)拋出OutOfMemoryError異常。
●?通常會(huì)將-Xms和-Xmx兩個(gè)參數(shù)配置相同的值,其目的是為了能夠在java垃圾回收機(jī)制清理完堆區(qū)后不需要重新分隔計(jì)算堆區(qū)的大小,從而提高性能。
●?默認(rèn)情況下
??初始內(nèi)存大小:物理電腦內(nèi)存大小/ 64
??最大內(nèi)存大小:物理電腦內(nèi)存大小/ 4
年輕代和老年代

●? 存儲(chǔ)在JVM中的Java對(duì)象可以被劃分為兩類:
??一類是生命周期較短的瞬時(shí)對(duì)象,這類對(duì)象的創(chuàng)建和消亡都非常迅速
??另外一類對(duì)象的生命周期卻非常長(zhǎng),在某些極端的情況下還能夠與JVM的生命周期保持一致
●?Java堆區(qū)進(jìn)一步細(xì)分的話, 可以劃分為年輕代(YoungGen)和老年代(OldGen)
●?其中年輕代又可以劃分為Eden空間、Survivor0空間和Survivor1空間(有時(shí)也叫做from區(qū)、to區(qū))。
●?在HotSpot中, Eden空間和另外兩個(gè)Survivor空間缺省所占的比例是8:1:1
●?當(dāng)然開發(fā)人員可以通過選項(xiàng)“-XX:SurvivorRatio”調(diào)整這個(gè)空間比例。比如-XX: SurvivorRatio=8
●?幾乎所有的Java對(duì)象都是在Eden區(qū)被new出來的。
●?絕大部分的Java對(duì)象的銷毀都在新生代進(jìn)行了。
??IBM公司的專門研究表明,新生代中80%的對(duì)象都是“朝生夕死”的。
●?可以使用選項(xiàng)" -Xmn"設(shè)置新生代最大內(nèi)存大小
??這個(gè)參數(shù)一般使用默認(rèn)值就可以了。

配置新生代與老年代在堆結(jié)構(gòu)的占比。
●?默認(rèn)-XX: NewRatio=2,表示新生代占1,老年代占2,新生代占整個(gè)堆的1/3
●?可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個(gè)堆的1/5

對(duì)象分配過程
概述
為新對(duì)象分配內(nèi)存是一件非常嚴(yán)謹(jǐn)和復(fù)雜的任務(wù),JVM的設(shè)計(jì)者們不僅需要考慮內(nèi)存如何分配、在哪里分配等問題,并且由于內(nèi)存分配算法與內(nèi)存回收算法密切相關(guān),所以還需要考慮GC執(zhí)行完內(nèi)存回收后是否會(huì)在內(nèi)存空間中停生內(nèi)存碎片。
1、new的對(duì)象先放伊甸園區(qū)。此區(qū)有大小限制。
2、當(dāng)伊甸園的空間填滿時(shí),程序又需要?jiǎng)?chuàng)建對(duì)象,JVM的垃圾回收器將對(duì)伊甸園區(qū)進(jìn)行垃圾回收(Minor GC), 將伊甸園區(qū)中的不再被其他對(duì)象所引用的對(duì)象進(jìn)行銷毀。再加載新的對(duì)象放到伊甸園區(qū)中
3、然后將伊甸園中的剩余對(duì)象移動(dòng)到幸存者0區(qū)。
4、如果再次觸發(fā)垃圾回收,此時(shí), 上次幸存下來的放到幸存者0區(qū)的,如果沒有回收,就會(huì)
放到幸存者1區(qū)。
5、如果再次經(jīng)歷垃圾回收,此時(shí)會(huì)重新放回幸存者0區(qū),接著再去幸存者1區(qū)。
6、啥時(shí)候能去養(yǎng)老區(qū)呢?可以設(shè)置次數(shù)。默認(rèn)是15次。可以設(shè)置參數(shù): -XX:MaxTenuringThreshold=<N> 進(jìn)行設(shè)置。




總結(jié)
針對(duì)幸存者s0,s1區(qū)的總結(jié):復(fù)制之后有交換,誰空誰是to.
關(guān)于垃圾回收:頻繁在新生區(qū)收集,很少在養(yǎng)老區(qū)收集,幾乎不在永久區(qū)/元空間收集。
Minor GC、Major GC與Full GC
概述
JVM在進(jìn)行GC時(shí),并非每次都對(duì)上面三個(gè)內(nèi)存區(qū)域一起回收的,大部分時(shí)候回收的都是指新生代。
針對(duì)HotSpot VM的實(shí)現(xiàn),它里面的GC按照回收區(qū)域又分為兩大種類型:一種種是部分收集(Partial GC),一種是整堆收集(Full GC)
●?部分收集: 不是完整收集整個(gè)Java堆的垃圾收集。其中又分為:
??新生代收集(Minor GC / Young GC):只是新生代的垃圾收集
??老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
???目前,只有CMS GC會(huì)有單獨(dú)收集老年代的行為。
???注意,很多時(shí)候Major GC會(huì)和Full GC混淆使用,需要具體分辨是老年代回收還是整堆回收。
??混合收集(Mixed GC):收集整個(gè)新生代以及部分老年代的垃圾收集。
???目前,只有G1 GC會(huì)有這種行為
●?整堆收集(Full GC):收集整個(gè)java堆和方法區(qū)的垃圾收集。
最簡(jiǎn)單的分代式GC策略的觸發(fā)條件
●?年輕代GC(Minor GC)觸發(fā)機(jī)制:
??當(dāng)年輕代空間不足時(shí),就會(huì)觸發(fā)Minor GC, 這里的年輕代滿指的是Eden代滿,Survivor滿不會(huì)引發(fā)GC。(每次 Minor GC會(huì)清理年輕代的內(nèi)存)。
??因?yàn)镴ava對(duì)象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。這一定義既清晰又易于理解。
??Minor GC會(huì)引發(fā)STW,暫停其它用戶的線程,等垃圾回收結(jié)束,用戶線程才恢復(fù)運(yùn)行。

●?老年代GC(Major GC/Full GC)觸發(fā)機(jī)制:
??指發(fā)生在老年代的GC,對(duì)象從老年代消失時(shí),我們說"Major GC"或"Full GC"發(fā)生了。
??出現(xiàn)了Major GC, 經(jīng)常會(huì)伴隨至少一次的Minor GC (但非絕對(duì)的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程)。
???也就是在老年代空間不足時(shí),會(huì)先嘗試觸發(fā)Minor GC。如果之后空間還不足,則觸發(fā)Major GC
??Major GC的速度一般會(huì)比Minor GC慢10倍以上,STW的時(shí)間更長(zhǎng)。
??如果Major GC后,內(nèi)存還不足,就報(bào)OOM了。
●?Full GC觸發(fā)機(jī)制:
觸發(fā)Full GC執(zhí)行的情況有如下五種:
(1)調(diào)用System.gc()時(shí),系統(tǒng)建議執(zhí)行Full GC, 但是不必然執(zhí)行
(2)老年代空間不足
(3)方法區(qū)空間不足
(4)通過Minor GC后進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
(5)由Eden區(qū)、survivor space0 (From Space)區(qū)向survivor space1 (To Space) 區(qū)復(fù)制時(shí),對(duì)象大小大于To Space可用內(nèi)存,則把該對(duì)象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對(duì)象大小
說明:Full GC是開發(fā)或調(diào)優(yōu)中盡量要避免的。這樣暫時(shí)時(shí)間會(huì)短一些。
堆空間分代思想
為什么需要把Java堆分代?不分代就不能正常工作了嗎?
●?經(jīng)研究,不同對(duì)象的生命周期不同。70%-99%的對(duì)象是臨時(shí)對(duì)象。
??新生代:有Eden、兩塊大小相同的Survivor (又稱為from/to,s0/s1)構(gòu)成,to總為空。
??老年代:存放新生代中經(jīng)歷多次GC仍然存活的對(duì)象。
●?其實(shí)不分代完全可以,分代的唯一理由就是優(yōu)化GC性能。如果沒有分代,那所有的對(duì)象都在一塊,就如同把一個(gè)學(xué)校的人都關(guān)在一個(gè)教室。GC的時(shí)候要找到哪些對(duì)象沒用,這樣就會(huì)對(duì)堆的所有區(qū)域進(jìn)行掃描。而很多對(duì)象都是朝生夕死的,如果分代的話,把新創(chuàng)建的對(duì)象放到某一地方,當(dāng)GC的時(shí)候先把這塊存儲(chǔ)“朝生夕死”對(duì)象的區(qū)域進(jìn)行回收,這樣就會(huì)騰出很大的空間出來。


內(nèi)存分配策略
概述
如果對(duì)象在Eden出生并經(jīng)過第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間中,并將對(duì)象年齡設(shè)為1。對(duì)象在Survivor 區(qū)中每熬過一 次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲,其實(shí)每個(gè)JVM、每個(gè)GC都有所不同)時(shí),就會(huì)被晉升到老年代中。
對(duì)象晉升老年代的年齡閾值,可以通過選項(xiàng)-XX :MaxTenuringThreshold來設(shè)置。
對(duì)象提升(Promotion)規(guī)則
針對(duì)不同年齡段的對(duì)象分配原則如下所示:
●?優(yōu)先分配到Eden
●?大對(duì)象直接分配到老年代
??盡量避免程序中出現(xiàn)過多的大對(duì)象
●?長(zhǎng)期存活的對(duì)象分配到老年代
●?動(dòng)態(tài)對(duì)象年齡判斷
??如果Survivor 區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代,無須等到MaxTenur ingThreshold中要求的年齡。
●?空間分配擔(dān)保
??-XX: HandlePromotionFailure
對(duì)象分配過程:TLAB
為什么有TLAB ( Thread Local Allocation Buffer ) ?
●?堆區(qū)是線程共享區(qū)域,任何線程都可以訪問到堆區(qū)中的共享數(shù)據(jù)
●?由于對(duì)象實(shí)例的創(chuàng)建在JVM中非常頻繁,因此在并發(fā)環(huán)境下從堆區(qū)中劃分內(nèi)存空間是線程不安全的
●?為避免多個(gè)線程操作同一地址,需要使用加鎖等機(jī)制,進(jìn)而影響分配速度。
什么是TLAB?
●?從內(nèi)存模型而不是垃圾收集的角度,對(duì)Eden區(qū)域繼續(xù)進(jìn)行劃分,JVM為每個(gè)線程分配了一個(gè)私有緩存區(qū)域,它包含在Eden空間內(nèi)。
●?多線程同時(shí)分配內(nèi)存時(shí),使用TLAB可以避免一系列的非線程安全問題,同時(shí)還能夠提升內(nèi)存分配的吞吐量,因此我們可以將這種內(nèi)存分配方式稱之為快速分配策略。
●?據(jù)所知所有OpenJDK衍生出來的JVM都提供了TLAB的設(shè)計(jì)。

TLAB的再說明:
●?盡管不是所有的對(duì)象實(shí)例都能夠在TLAB中成功分配內(nèi)存,但JVM確實(shí)是將TLAB作為內(nèi)存分配的首選。
●?在程序中,開發(fā)人員可以通過選項(xiàng)“-XX:UseTLAB”設(shè)置是否開啟TLAB空間。
●?默認(rèn)情況下,TLAB空間的內(nèi)存非常小,僅占有整個(gè)Eden空間的1%,當(dāng)然我們可以通過選項(xiàng)"-XX:TLABWasteTargetPercent"設(shè)置TLAB空間所占用Eden空間的百分比大小。
●?一旦對(duì)象在TLAB空間分配內(nèi)存失敗時(shí),JVM就會(huì)嘗試著通過使用加鎖機(jī)制確保數(shù)據(jù)操作的原子性,從而直接在Eden空間中分配內(nèi)存。

堆空間的參數(shù)設(shè)置
●?官網(wǎng)說明:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
●?-XX:+PrintFlagsInitial:查看所有的參數(shù)的默認(rèn)初始值,
●?-XX:+PrintFlagsFinal:查看所有的參數(shù)的最終值(可能會(huì)存在修改,不再是初始值)
●?-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)的占比
堆是分配對(duì)象存儲(chǔ)的唯一選擇嗎?NO
在《深入理解Java虛擬機(jī)》中關(guān)于Java堆內(nèi)存有這樣一段描述:
隨著JIT編譯期的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化,所有的對(duì)象都分配到堆上也漸漸變得不那么“絕對(duì)”了。
在Java虛擬機(jī)中,對(duì)象是在Java堆中分配內(nèi)存的,這是一個(gè)普遍的常識(shí)。但是,有一種特殊情況,那就是如果經(jīng)過逃逸分析(Escape Analysis) 后發(fā)現(xiàn),一個(gè)對(duì)象并沒有逃逸出方法的話,那么就可能被優(yōu)化成棧上分配。這樣就無需在堆上分配內(nèi)存,也無須進(jìn)行垃圾回收了。這也是最常見的堆外存儲(chǔ)技術(shù)。
此外,前面提到的基于OpenJDK深度定制的TaoBaoVM,其中創(chuàng)新的GCIH (GCinvisible heap) 技術(shù)實(shí)現(xiàn)off-heap,將生命周期較長(zhǎng)的Java對(duì)象從heap中移至heap外,并且GC不能管理GCIH內(nèi)部的Java對(duì)象,以此達(dá)到降低GC的回收頻率和提升GC的回收效率的目的。
逃逸分析概述
●?如何將堆上的對(duì)象分配到棧,需要使用逃逸分析手段。
●?這是一種可以有效減少Java程序中同步負(fù)載和內(nèi)存堆分配壓力的跨函數(shù)全局?jǐn)?shù)據(jù)流分析算法。
●?通過逃逸分析,Java Hotspot編譯器能夠分析出一個(gè)新的對(duì)象的引用的使用范圍從而決定是否要將這個(gè)對(duì)象分配到堆上。
●?逃逸分析的基本行為就是分析對(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ù)傳遞到其他地方中。
參數(shù)設(shè)置:
●?在JDK 6u23版本之后,HotSpot中默認(rèn)就已經(jīng)開啟了逃逸分析。如果使用的是較早的版本,開發(fā)人員則可以通過:
???選項(xiàng)“-XX:+DoEscapeAnalysis”顯式開啟逃逸分析
???通過選項(xiàng)“-XX:+PrintEscapeAnalysis”查看逃逸分析的篩選結(jié)果。
沒有發(fā)生逃逸的對(duì)象,則可以分配到棧上,隨著方法執(zhí)行的結(jié)束,??臻g就被移除。
如何快速的判斷是否發(fā)生了逃逸分析,大家就看new的對(duì)象是否有可能在方法外被調(diào)用。


結(jié)論
開發(fā)中能使用局部變量的,就不要使用在方法外定義。
逃逸分析:代碼優(yōu)化
使用逃逸分析,編譯器可以對(duì)代碼做如下優(yōu)化:
一、棧上分配。將堆分配轉(zhuǎn)化為棧分配。如果一個(gè)對(duì)象在子程序中被分配,要使指向該對(duì)象的指針永遠(yuǎn)不會(huì)逃逸,對(duì)象可能是棧分配的候選,而不是堆分配。
二、同步省略。如果一個(gè)對(duì)象被發(fā)現(xiàn)只能從一個(gè)線程被訪問到,那么對(duì)于這個(gè)對(duì)象的操作可以不考慮同步。
三、分離對(duì)象或標(biāo)量替換。有的對(duì)象可能不需要作為一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問到,那么對(duì)象的部分(或全部)可以不存儲(chǔ)在內(nèi)存,而是存儲(chǔ)在CPU寄存器中。
優(yōu)化代碼之棧上分配
●?JIT編譯器在編譯期間根據(jù)逃逸分析的結(jié)果,發(fā)現(xiàn)如果一個(gè)對(duì)象并沒有逃逸出方法的話,就可能被優(yōu)化成棧上分配。分配完成后,繼續(xù)在調(diào)用棧內(nèi)執(zhí)行,最后線程結(jié)束,??臻g被回收,局部變量對(duì)象也被回收。這樣就無須進(jìn)行垃圾回收了
●?常見的棧上分配的場(chǎng)景
???在逃逸分析中,已經(jīng)說明了。分別是給成員變量賦值、方法返回值、實(shí)例引用傳遞。
優(yōu)化代碼之同步省略(消除)
●?線程同步的代價(jià)是相當(dāng)高的,同步的后果是降低并發(fā)性和性能。
●?在動(dòng)態(tài)編譯同步塊的時(shí)候,JIT編譯器可以借助逃逸分析來判斷同步塊所使用的鎖對(duì)象是否只能夠被一個(gè)線程訪問而沒有被發(fā)布到其他線程。如果沒有,那么JIT編譯器在編譯這個(gè)同步塊的時(shí)候就會(huì)取消對(duì)這部分代碼的同步。這樣就能大大提高并發(fā)性和性能。這個(gè)取消同步的過程就叫同步省略,也叫鎖消除。


優(yōu)化代碼之標(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è)成員變量來代替。這個(gè)過程就是標(biāo)量替換。


可以看到,Point這個(gè)聚合量經(jīng)過逃逸分析后,發(fā)現(xiàn)他并沒有逃逸,就被替換成兩個(gè)聚合量了。那么標(biāo)量替換有什么好處呢?就是可以大大減少堆內(nèi)存的占用。因?yàn)橐坏恍枰獎(jiǎng)?chuàng)建對(duì)象了,那么就不再需要分配堆內(nèi)存了。標(biāo)量替換為棧上分配提供了很好的基礎(chǔ)。
標(biāo)量替換參數(shù)設(shè)置
參數(shù)-XX: +EliminateAllocations:開啟了標(biāo)量替換(默認(rèn)打開),允許將對(duì)象打散分配在棧上。
逃逸分析小結(jié)

●?參數(shù)-server:啟動(dòng)Server模式,因?yàn)樵赟erver模式下,才可以啟用逃逸分析。
●?參數(shù)-XX:+DoEscapeAnalysis: 啟用逃逸分析
●?參數(shù)-Xmx10m:指定了堆空間最大為10MB
●?參數(shù)-XX:+PrintGC: 將打印GC日志。
●?參數(shù)-XX: +EliminateAllocations:開啟了標(biāo)量替換(默認(rèn)打開),允許將對(duì)象打散分配在棧上,比如對(duì)象擁有id和name兩個(gè)字段,那么這兩個(gè)字段將會(huì)被視為兩個(gè)獨(dú)立的局部變量進(jìn)行分配。
逃逸分析并不成功
●?關(guān)于逃逸分析的論文在1999年就已經(jīng)發(fā)表了,但直到JDK 1.6才有實(shí)現(xiàn),而且這項(xiàng)技術(shù)到如今也并不是十分成熟的。|
●?其根本原因就是無法保證逃逸分析的性能消耗一定能高于他的消耗。雖然經(jīng)過逃逸分析可以做標(biāo)量替換、棧上分配、和鎖消除。但是逃逸分析自身也是需要進(jìn)行一系列復(fù)雜的分析的,這其實(shí)也是一個(gè)相對(duì)耗時(shí)的過程。
●?一個(gè)極端的例子,就是經(jīng)過逃逸分析之后,發(fā)現(xiàn)沒有一個(gè)對(duì)象是不逃逸的。那這個(gè)逃逸分析的過程就白白浪費(fèi)掉了。
●?雖然這項(xiàng)技術(shù)并不十分成熟,但是它也是即時(shí)編譯器優(yōu)化技術(shù)中一個(gè)十分重要的手段。
●?注意到有一些觀點(diǎn),認(rèn)為通過逃逸分析,JVM會(huì)在棧上分配那些不會(huì)逃逸的對(duì)象,這在理論上是可行的,但是取決于JVM設(shè)計(jì)者的選擇。據(jù)所知,Oracle Hotspot JVM中并未這么做,這一點(diǎn)在逃逸分析相關(guān)的文檔里已經(jīng)說明,所以可以明確所有的對(duì)象實(shí)例都是創(chuàng)建在堆,上。
●?目前很多書籍還是基于JDK 7以前的版本,JDK已經(jīng)發(fā)生了很大變化,intern字符串的緩存和靜態(tài)變量曾經(jīng)都被分配在永久代上,而永久代已經(jīng)被元數(shù)據(jù)區(qū)取代。但是,intern字符串緩存和靜態(tài)變量并不是被轉(zhuǎn)移到元數(shù)據(jù)區(qū),而是直接在堆上分配,所以這一點(diǎn)同樣符合前面一點(diǎn)的結(jié)論:對(duì)象實(shí)例都是分配在堆上。
堆(Heap)小結(jié)
●?年輕代是對(duì)象的誕生、成長(zhǎng)、消亡的區(qū)域,一個(gè)對(duì)象在這里產(chǎn)生、應(yīng)用,最后被垃圾回收器收集、結(jié)束生命。
●?老年代放置長(zhǎng)生命周期的對(duì)象,通常都是從Survivor區(qū)域篩選拷貝過來的Java對(duì)象。當(dāng)然,也有特殊情況,我們知道普通的對(duì)象會(huì)被分配在TLAB上;如果對(duì)象較大,JVM會(huì) 試圖直接分配在Eden其他位置上;如果對(duì)象太大,完全無法在新生代找到足夠長(zhǎng)的連續(xù)空閑空間,JVM就會(huì)直接分配到老年代。
●?當(dāng)GC只發(fā)生在年輕代中,回收年輕代對(duì)象的行為被稱為Minor GC。當(dāng)GC發(fā)生在老年代時(shí)則被稱為Major GC或者Full GC。一般的,Minor GC的發(fā)生頻率要比Major GC高很多,即老年代中垃圾回收發(fā)生的頻率將大大低于年輕代。