1筹燕、JVM內(nèi)存結(jié)構(gòu)
所以通過(guò)表格的形式概括如下:
注:方法區(qū),位于堆的持久代中蛹含。但1.8以后毅厚,移除持久代,采用元空間浦箱。
2吸耿、堆內(nèi)存結(jié)構(gòu)
JVM參數(shù)
-Xmx: 表示java虛擬機(jī)堆區(qū)內(nèi)存可被分配的最大上限祠锣,通常為操作系統(tǒng)可用內(nèi)存的1/4大小。開(kāi)發(fā)過(guò)程中咽安,通常會(huì)將 -Xms 與 -Xmx兩個(gè)參數(shù)的配置相同的值伴网,其目的是為了能夠在java垃圾回收機(jī)制清理完堆區(qū)后不需要重新分隔計(jì)算堆區(qū)的大小而浪費(fèi)資源。
新生代:
大部分對(duì)象迅速死亡妆棒,存活對(duì)象少澡腾,因而采用“標(biāo)記-清除-復(fù)制”算法。分為eden區(qū)糕珊,s0,动分、s1區(qū)。
新生對(duì)象大部分分配在eden區(qū)红选,當(dāng)eden區(qū)無(wú)法再繼續(xù)分配新對(duì)象時(shí)(不一定eden區(qū)滿(mǎn))澜公,觸發(fā)Minor GC,將消亡的對(duì)象清理掉(作用于 E 區(qū)喇肋、S0區(qū)及 S1 區(qū))坟乾,并將剩余的對(duì)象復(fù)制到 S0 區(qū),此時(shí) S1 區(qū)是空的苟蹈。下一次eden區(qū)無(wú)法再繼續(xù)分配新對(duì)象時(shí)糊渊,,觸發(fā)Minor GC慧脱,并將剩余的對(duì)象復(fù)制到S1區(qū)渺绒,此時(shí)S0區(qū)是空的。也就是說(shuō)S0和S1總有一個(gè)保持為空菱鸥。
當(dāng)新生代對(duì)象經(jīng)歷幾次MinorGC后(HotSpot 默認(rèn)為 15 次宗兼,可通過(guò)-XX:MaxTenuringThreshold控制),會(huì)晉升至老年代氮采。
新生代對(duì)象分配機(jī)制:
jvm參數(shù):
-xmn用于設(shè)置年輕代的大小(等同-XX:newSize = -XX:MaxnewSize = -Xmn )殷绍,建議設(shè)為整個(gè)堆大小的1/3或者1/4,兩個(gè)值設(shè)為一樣大。
-XX:SurvivorRatio 用于設(shè)置Eden和其中一個(gè)Survivor的比值鹊漠,一般E區(qū)和S0主到、S1區(qū)比值是8:1:1。
.-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用于設(shè)置晉升到老年代的對(duì)象年齡的最小值和最大值躯概,每個(gè)對(duì)象在堅(jiān)持過(guò)一次Minor GC之后登钥,年齡就加1。
-XX:+UseTLAB ?默認(rèn)啟用
-XX:TLABWasteTargetPercent:TLAB占eden區(qū)的百分比,默認(rèn)1%
老年代:
老年代采用“標(biāo)記-清除-整理”算法娶靡。
老年代GC為MajorGC牧牢,一般MajorGC都會(huì)伴隨MinorGC(因?yàn)閷?duì)象一般都從新生代晉升老年代,導(dǎo)致老年代空間不足,觸發(fā)MajorGC塔鳍。但非絕對(duì)的伯铣,在 ParallelScavenge 收集器的收集策略里
就有直接進(jìn)行 Major GC 的策略選擇過(guò)程)。FullGC為一次majorGC和MinorGC轮纫。
永久代:
存放常量池和方法區(qū)數(shù)據(jù)腔寡。
JVM參數(shù):
-XX:PermSize和-XX:MaxPermSize來(lái)指定最小值和最大值
3、內(nèi)存分配與回收策略
(1)蜡感、對(duì)象優(yōu)先在Eden分區(qū):
大多數(shù)情況下蹬蚁,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒(méi)有足夠空間分配時(shí)郑兴,虛擬機(jī)發(fā)起一次Minor GC犀斋。GC后對(duì)象嘗試放入Survivor空間,如果Survivor空間無(wú)法放入對(duì)象時(shí)情连,只能通過(guò)空間分配擔(dān)保機(jī)制提前轉(zhuǎn)移到老年代叽粹。
(2)、大對(duì)象直接進(jìn)入老年代:
大對(duì)象指需要大量連續(xù)內(nèi)存空間的Java對(duì)象却舀。虛擬機(jī)提供-XX:PretenureSizeThreshold參數(shù)虫几,如果大于這個(gè)設(shè)置值對(duì)象則直接分配在老年代。這樣可以避免新生代中的Eden區(qū)及兩個(gè)Survivor區(qū)發(fā)生大量?jī)?nèi)存復(fù)制挽拔。
(3)辆脸、長(zhǎng)期存活的對(duì)象進(jìn)入老年代:
虛擬機(jī)會(huì)給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器。如果對(duì)象在Eden出生并且經(jīng)過(guò)一次Minor GC后任然存活螃诅,且能夠被Survivor容納啡氢,將被移動(dòng)到Survivor空間中,并且對(duì)象年齡設(shè)為1.每次Minor GC后對(duì)象任然存活在Survivor區(qū)中术裸,年齡就加一倘是,當(dāng)年齡到達(dá)-XX:MaxTenuringThreshold參數(shù)設(shè)定的值時(shí),將會(huì)移動(dòng)到老年代袭艺。
(4)搀崭、動(dòng)態(tài)年齡判斷:
虛擬機(jī)不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到-XX:MaxTenuringThreshold設(shè)定的值才會(huì)將對(duì)象移動(dòng)到老年代去。如果Survivor中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半猾编,年齡大于或等于該年齡的對(duì)象可以直接進(jìn)入老年代瘤睹。
(5)、空間分配擔(dān)保:
在Minor GC前答倡,虛擬機(jī)會(huì)檢查老年代最大可用連續(xù)空間是否大于新生代所有對(duì)象總空間默蚌,如果條件成立,那么Minor GC是成立的苇羡。如果不成立,虛擬機(jī)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。如果允許设江,那么會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次移動(dòng)到老年代對(duì)象的平均大小锦茁,如果大于,將嘗試一次Minor GC叉存。如果小于码俩,或者HandlePromotionFailure設(shè)置值不允許冒險(xiǎn),那將進(jìn)行一次Full GC歼捏。
minorgc時(shí)稿存,會(huì)去檢查老年代對(duì)象引用新生代對(duì)象的引用。老年代提供一個(gè)專(zhuān)門(mén)區(qū)域瞳秽,存放這些信息瓣履,提高minorgc檢查效率。
4练俐、觸發(fā)FullGC的條件:
老年代空間不足袖迎、
永久代空間不足、
GC擔(dān)保失敗腺晾、
CMS的Cocurrent mode failure(發(fā)生在cms的清理sweep階段,發(fā)現(xiàn)有新的垃圾產(chǎn)生,而且老年代沒(méi)有足夠空間導(dǎo)致的)
5燕锥、對(duì)象的創(chuàng)建過(guò)程和內(nèi)存分配
指針碰撞(Bump the Pointer):假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過(guò)的內(nèi)存都放在一邊悯蝉,空閑的內(nèi)存放在另一邊归形,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離鼻由。
空閑列表(Free List):如果Java堆中的內(nèi)存并不是規(guī)整的暇榴,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那么虛擬機(jī)維護(hù)一個(gè)列表嗡靡,并更新列表山的記錄跺撼。
分配內(nèi)存過(guò)程中還需要解決線(xiàn)程安全問(wèn)題。 一個(gè)修改指針操作讨彼,就會(huì)帶來(lái)隱患:對(duì)象 A 正分配內(nèi)存呢歉井,指針還沒(méi)來(lái)得急修改,突然對(duì)象 B 又同時(shí)使用了原來(lái)的指針來(lái)分配 B 的內(nèi)存哈误。解決方案也有兩種:同步處理——實(shí)際上虛擬機(jī)采用 CAS 配上失敗重試來(lái)保證更新操作的原子性哩至。把內(nèi)存分配的動(dòng)作按照線(xiàn)程劃分在不同的空間之中進(jìn)行,即每個(gè)線(xiàn)程在 Java 堆中預(yù)先分配一小塊內(nèi)存蜜自,成為本地線(xiàn)程分配緩存(Thread Local Allocation Buffer菩貌,TLAB)。哪個(gè)線(xiàn)程要分配內(nèi)存重荠,就在哪個(gè)線(xiàn)程的 TLAB 上分配箭阶,用完并分配新的TLAB時(shí),才需要同步鎖定。
對(duì)象頭中主要包括兩部分信息:
一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)仇参,如哈希碼嘹叫、GC分代年齡、鎖狀態(tài)標(biāo)志诈乒、線(xiàn)程持有的鎖罩扇、偏向線(xiàn)程ID攒霹、偏向時(shí)間戳等验靡。
另一部分是類(lèi)型指針,即對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針杠步,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例肠鲫。如果對(duì)象是Java數(shù)組员帮,那在對(duì)象頭中還必須有一塊記錄數(shù)組長(zhǎng)的數(shù)據(jù)。
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息滩届,也是程序代碼中定義的各種類(lèi)型的字段內(nèi)容集侯。從父類(lèi)繼承下來(lái)的,在子類(lèi)中定義的都需要記錄下來(lái)帜消。
對(duì)齊填充僅僅起到占位符的作用棠枉。HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址是8字節(jié)的整數(shù)倍,所以對(duì)象大小必須是8字節(jié)的整數(shù)倍泡挺。當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí)辈讶,需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。
注:逃逸分析與棧上分配
逃逸分析:就是分析對(duì)象動(dòng)態(tài)作用域:當(dāng)一個(gè)對(duì)象在方法中被定義后娄猫,它可能被外部方法所引用贱除。方法逃逸:例如作為調(diào)用參數(shù)傳遞到其他方法中。線(xiàn)程逃逸:有可能被外部線(xiàn)程訪(fǎng)問(wèn)到媳溺,譬如賦值給類(lèi)變量或可以在其他線(xiàn)程中訪(fǎng)問(wèn)的實(shí)例變量月幌。
棧上分配(Stack Allocation):
如果確定一個(gè)對(duì)象不會(huì)逃逸出方法之外,那讓這個(gè)對(duì)象在棧上分配內(nèi)存將會(huì)是一個(gè)很不錯(cuò)的主意悬蔽,對(duì)象所占用的內(nèi)存空間就可以隨棧幀出棧而銷(xiāo)毀扯躺。在一般應(yīng)用中,不會(huì)逃逸的局部對(duì)象所占的比例很大蝎困,如果能使用棧上分配录语,那大量的對(duì)象就會(huì)隨著方法的結(jié)束而自動(dòng)銷(xiāo)毀了,垃圾收集系統(tǒng)的壓力將會(huì)小很多禾乘。在實(shí)際的應(yīng)用程序澎埠,尤其是大型程序中反而發(fā)現(xiàn)實(shí)施逃逸分析可能出現(xiàn)效果不穩(wěn)定的情況,或因分析過(guò)程耗時(shí)但卻無(wú)法有效判別出非逃逸對(duì)象而導(dǎo)致性能(即時(shí)編譯的收益)有所下降始藕,所以在很長(zhǎng)的一段時(shí)間里蒲稳,即使是Server Compiler氮趋,也默認(rèn)不開(kāi)啟逃逸分析,甚至在某些版本(如JDK 1.6 Update18)中還曾經(jīng)短暫地完全禁止了這項(xiàng)優(yōu)化弟塞。
6凭峡、對(duì)象回收
對(duì)象存活的判定
引用計(jì)數(shù)法:存在循環(huán)引用問(wèn)題。
Root搜索法(采用)
以下對(duì)象會(huì)被認(rèn)為是root對(duì)象:
被啟動(dòng)類(lèi)(bootstrap加載器)加載的類(lèi)和創(chuàng)建的對(duì)象
jvm運(yùn)行時(shí)方法區(qū)類(lèi)靜態(tài)變量(static)引用的對(duì)象
jvm運(yùn)行時(shí)方法去常量池引用的對(duì)象
jvm當(dāng)前運(yùn)行線(xiàn)程中的虛擬機(jī)棧變量表引用的對(duì)象
本地方法棧中(jni)引用的對(duì)象
注:由于finalize方法只會(huì)被JVM調(diào)用一次决记。
public class FinalizerTest {
public static FinalizerTest object;
public void isAlive() {
System.out.println("I'm alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("method finalize is running");
object = this;
}
public static void main(String[] args) throws Exception {
object = new FinalizerTest();
// 第一次執(zhí)行,finalize方法會(huì)自救
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
// 第二次執(zhí)行倍踪,finalize方法已經(jīng)執(zhí)行過(guò)
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
}
}
執(zhí)行結(jié)果:
method finalize is running
I'm alive
I'm dead
方法區(qū)的回收主要包括兩部分內(nèi)容:廢棄常量和無(wú)用的類(lèi)系宫。
廢棄常量的回收與回收J(rèn)ava堆中的對(duì)象類(lèi)似。
判斷無(wú)用的類(lèi)的條件必須滿(mǎn)足三個(gè)條件:
該類(lèi)所有實(shí)例已經(jīng)被回收建车。
加載該類(lèi)的ClassLoader已被回收扩借。
該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,也無(wú)法通過(guò)反射訪(fǎng)問(wèn)該類(lèi)缤至。
謝謝以下分享