[筆記](méi) 深入了解 JVM 底層原理

對(duì)應(yīng)視頻地址:

(上集):https://www.bilibili.com/video/BV1BT4y1G73q

(下集):https://www.bilibili.com/video/BV13Z4y147mt

筆記MarkDown版下載地址:https://wwa.lanzous.com/i7TxGj2mhrc

本文章原創(chuàng)博客地址:https://blog.mcplugin.cn/p/699


1. 操作數(shù)棧(OS)和本地變量表(LVA)

本地變量表 (LVA)

本地變量表是一個(gè)以0為起始下標(biāo)的字?jǐn)?shù)組拜秧,它包含了所有參數(shù)和局部變量,每一個(gè)slot都是4個(gè)字節(jié)(Byte)大小

int乃秀、float和reference類型的值在數(shù)組中占據(jù)1個(gè)slot,即4個(gè)字節(jié)峻呛。

double和long的值在數(shù)組中占據(jù)2個(gè)連續(xù)的slot迫肖,即總共8個(gè)字節(jié)景醇。

Byte欲侮、short和char的值在存儲(chǔ)前會(huì)被轉(zhuǎn)換為int類型崭闲,占據(jù)1個(gè)slot,即4個(gè)字節(jié)威蕉。

但是不同的 JVM 對(duì) Boolean 的存儲(chǔ)方式是不同的刁俭。但大多數(shù)JVM在局部變量數(shù)組中給Boolean提供了1個(gè)slot。

參數(shù)會(huì)按照聲明的順序放入局部變量數(shù)組中忘伞。

操作數(shù)棧 (OS)

JVM使用操作數(shù)棧作為工作空間薄翅,是用來(lái)存儲(chǔ)中間計(jì)算的結(jié)果的沙兰。

操作數(shù)棧的組織形式是像本地變量表 (LVA) 一樣的字?jǐn)?shù)組氓奈,每一個(gè)slot占用4個(gè)字節(jié)(Byte)。但它不像本地變量數(shù)組那樣使用索引來(lái)訪問(wèn)鼎天,而是通過(guò)一些指令來(lái)訪問(wèn) (OS是一個(gè)Stack模型舀奶,可以進(jìn)行push或者pop) 這些指令可以將值推送到操作數(shù)棧(如:push),一些指令(如:pop)可以從操作數(shù)棧中彈出值 還有一些指令可以執(zhí)行必要的操作斋射。例:50 + 20 = ?JVM內(nèi)運(yùn)行環(huán)境的OSLVA育勺,以及其對(duì)應(yīng)的字節(jié)碼的操作

iload_0 把一個(gè)int值從本地變量表中index為0的地方壓入操作數(shù)棧

iload_1 把一個(gè)int值從本地變量表中index為1的地方壓入操作數(shù)棧

將棧頂兩int型數(shù)值相減并將結(jié)果壓入棧頂

istore_2 講一個(gè)int值從操作數(shù)棧中彈出,并存儲(chǔ)到本地變量表index為2的地方

(資料來(lái)源:Java Virtual Machine (JVM) Stack Area - GeeksforGeeks)

2. this指針是何時(shí)賦值的

JVM字節(jié)碼:

對(duì)應(yīng)的Java代碼

這段代碼的0~7是對(duì)一個(gè)Test4對(duì)象進(jìn)行new

在執(zhí)行invokespecial指令的時(shí)候進(jìn)行的賦值罗岖。執(zhí)行完畢invokespecial之后涧至,才回去執(zhí)行<com/qimingnan/jvm/Test4.<init>>調(diào)用Test4的構(gòu)造器

invokespecial:調(diào)用超類構(gòu)建方法, 實(shí)例初始化方法, 私有方法。

那么為什么要在invokespecial的時(shí)候進(jìn)行賦值呢桑包?

因?yàn)槲覀冊(cè)跇?gòu)造方法中南蓬,需要使用this指針。所以我們要在調(diào)用Test4的構(gòu)造器之前進(jìn)行this指針的賦值

所有的非靜態(tài)方法中的局部變量表中index0的位置,存放的都是this指針

2.1 new一個(gè)對(duì)象是否是線程安全的赘方?

new是非線程安全的**烧颖,因?yàn)橐痪?/p>

Java

Test4demo=newTest4

就有4句JVM字節(jié)碼來(lái)對(duì)應(yīng)實(shí)現(xiàn)。

0 new#2<com/qimingnan/jvm/Test4>

3 dup

4 invokespecial#3<com/qimingnan/jvm/Test4.<init>>

7 astore_1

這里7為什么是astore_1窄陡,而不是astore_0炕淮?

因?yàn)閍store_0存儲(chǔ)的是this指針,所以demo對(duì)象只能放在index1的位置

在main()方法中跳夭,index0存放的是args參數(shù)

3. 棧和棧幀的區(qū)別:

棧:每一個(gè)線程涂圆,都會(huì)有一個(gè)獨(dú)特自己的虛擬機(jī)棧(Stack),所以在棧里的數(shù)據(jù)是不會(huì)進(jìn)行線程互斥优妙,線程安全問(wèn)題的乘综。

棧幀:每調(diào)用一個(gè)方法,就會(huì)產(chǎn)生一個(gè)棧幀套硼。

4. return的時(shí)候都做了些什么卡辰?

如:ireturn

ireturn:從當(dāng)前方法返回int

不僅僅是這樣,當(dāng)JVM執(zhí)行ireturn的時(shí)候邪意,做了四件事:

1. 將局部變量表指針恢復(fù)成上一幀的指針

2. 將操作數(shù)棧的指針恢復(fù)成上一幀的指針

3. 將運(yùn)行時(shí)數(shù)據(jù)區(qū)內(nèi)的程序計(jì)數(shù)器改成調(diào)用invokevirtual <com/qimingnan/jvm/Test4.add>的下一個(gè)字節(jié)碼指令序號(hào) (如圖所示九妈,即:改成15)

4. 將add()方法占用的棧幀內(nèi)存全部回收

4.1額外思考:

如圖所示,

為什么add方法中的賦值操作雾鬼,需要先將10push到操作數(shù)棧(OS)萌朱,在istore到本地變量表(LVA)中呢?

而不是直接對(duì)本地變量表進(jìn)行操作呢策菜?

由于視頻中老師沒(méi)有進(jìn)行具體的解答晶疼,所以我在SOF進(jìn)行了提問(wèn),發(fā)現(xiàn)有人提出過(guò)類似的問(wèn)題

我發(fā)的帖子下面有人進(jìn)行的回復(fù)截圖:

我發(fā)現(xiàn)別人提問(wèn)的帖子地址:What is the role of operand stack in JVM? (stackoverflow.com)

有興趣的同學(xué)可以自行閱覽又憨。

5. 運(yùn)行時(shí)數(shù)據(jù)區(qū)內(nèi)幾個(gè)區(qū)域之間的關(guān)系

5.1 虛擬機(jī)棧 與 堆

如在一個(gè)方法中翠霍,有這么一句代碼:

Java

publicvoidtest{

Testdemo=newTest();

}

則,在test()方法的本地變量表中蠢莺,index1的位置寒匙,會(huì)有一個(gè)名為demo的對(duì)象,其引用內(nèi)容為堆中的對(duì)象內(nèi)容

6. 堆(Heap)

6.1 堆的默認(rèn)大絮锝:

最谐酢:1/64的物理內(nèi)存

最大:1/4的物理內(nèi)存

6.2 堆為什么要分新生代和老年代:

6.2.1 老年代存儲(chǔ)的內(nèi)容

大對(duì)象。什么是大對(duì)象祸憋?超過(guò)eden區(qū)会宪,就是大對(duì)象

GC年齡超過(guò)15。經(jīng)歷過(guò)15次GC

空間分配擔(dān)保一句話解釋JVM中空間分配擔(dān)保的問(wèn)題先解釋YGC(輕GC):當(dāng)對(duì)象生成在EDEN區(qū)失敗時(shí)蚯窥,出發(fā)一次YGC掸鹅,先掃描EDEN區(qū)中的存活對(duì)象喜命,進(jìn)入S0區(qū),S0放不下的進(jìn)入OLD區(qū)河劝,再掃描S1區(qū)壁榕,若存活次數(shù)超過(guò)閥值則進(jìn)入OLD區(qū),其它進(jìn)入S0區(qū)赎瞎,然后S0和S1交換一次牌里。那么當(dāng)發(fā)生YGC時(shí),JVM會(huì)首先檢查老年代最大的可用連續(xù)空間是否大于新生代所有對(duì)象的總和务甥,如果大于牡辽,那么這次YGC是安全的,如果不大于的話敞临,JVM就需要判斷HandlePromotionFailure是否允許空間分配擔(dān)保态辛。允許分配擔(dān)保:JVM繼續(xù)檢查老年代最大的可用連續(xù)空間是否大于歷次晉升到老年代的對(duì)象的平均大小,如果大于挺尿,則正常進(jìn)行一次YGC奏黑,盡管有風(fēng)險(xiǎn)(因?yàn)榕袛嗟氖瞧骄笮。锌赡苓@次的晉升對(duì)象比平均值大很多)编矾;如果小于熟史,或者HandlePromotionFailure設(shè)置不允許空間分配擔(dān)保,這時(shí)要進(jìn)行一次FGC窄俏。新生代采用的是復(fù)制收集算法蹂匹,S0和S1始終只是用其中一塊內(nèi)存區(qū),當(dāng)出現(xiàn)YGC后大部分對(duì)象仍然存活的話凹蜈,就需要老年代進(jìn)行分配擔(dān)保限寞,把survior區(qū)無(wú)法容納的對(duì)象直接晉升到老年代。那么這種空間分配擔(dān)保的前提是老年代還有容納的空間仰坦,一共有多少對(duì)象會(huì)活下來(lái)履植,在實(shí)際完成內(nèi)存回收之前是無(wú)法明確知道的,所以只好取之前每次回收晉升到老年代對(duì)象容量的平均值大小作為經(jīng)驗(yàn)值缎岗,與老年代的剩余空間比較静尼,決定是否進(jìn)行FGC(重GC)來(lái)讓老年代騰出更多空間白粉。

6.3 新生代中各大分區(qū)占比比例的由來(lái)

對(duì)大量程序經(jīng)過(guò)統(tǒng)計(jì)之后传泊,發(fā)現(xiàn)Eden區(qū)的對(duì)象只有5%~10%的對(duì)象會(huì)存活,所以在新生代中鸭巴,各大區(qū)域分布比例為8:1:1

7. 對(duì)象大小的計(jì)算方式

7.1 Class文件

Class文件開(kāi)始的兩個(gè)字(Word=2Byte)的大小的數(shù)據(jù)為cafe babe(咖啡Baby)

7.2 對(duì)象(Object)結(jié)構(gòu)

Mrk Word:用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)眷细,如哈希碼(HashCode)、GC分代年齡鹃祖、鎖狀態(tài)標(biāo)志溪椎、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等等校读。Mark Word在32位JVM中的長(zhǎng)度是32bit沼侣,在64位JVM中長(zhǎng)度是64bit。32位MarkWord數(shù)據(jù)結(jié)構(gòu)【共4B】:

類型指針:指向Class對(duì)象(即這個(gè)對(duì)象(Object)的模板)

數(shù)組長(zhǎng)度:若這個(gè)對(duì)象是數(shù)組歉秫,則此處存放數(shù)組長(zhǎng)度=====以上為頭部(Head)區(qū)域=====

實(shí)例數(shù)據(jù):類中定義的一些屬性等

對(duì)齊填充:在JDK里蛾洛,所有數(shù)據(jù)都是8Byte對(duì)齊。若一個(gè)對(duì)象數(shù)據(jù)為12Byte雁芙,則其后面必須填充4Byte轧膘,即12+4=16Byte 為8Byte的整數(shù)倍則只需要設(shè)置指針每次移動(dòng)9Byte,就可正確讀取內(nèi)存中的數(shù)據(jù)同理兔甘,Windows內(nèi)核中也是以4K對(duì)齊谎碍,稱之為頁(yè)(Page),以4M為大頁(yè)(Huge Page)為什么JVM要對(duì)齊填充洞焙?為了后面的指針壓縮

7.3 指針壓縮算法

開(kāi)啟指針壓縮的JVM參數(shù):-XX:+UseCompressedOops

64位地址分為堆的基地址+偏移量蟆淀,當(dāng)堆內(nèi)存<32GB時(shí)候,在壓縮過(guò)程中澡匪,把偏移量/8后保存到32位地址扳碍。在解壓再把32位地址放大8倍,所以啟用CompressedOops的條件是堆內(nèi)存要在4GB*8=32GB以內(nèi)仙蛉。

開(kāi)啟后笋敞,線性地址為4Byte,若不開(kāi)荠瘪,則為8Byte

指針壓縮在JDK6以后就是默認(rèn)開(kāi)啟的夯巷!若要關(guān)閉,則使用-XX:-UseCompressedOopsJVM參數(shù)

7.4 空對(duì)象大小

空對(duì)象是什么哀墓?空對(duì)象是沒(méi)有任何普通(靜態(tài))屬性的對(duì)象趁餐,而并非指指針為Null的對(duì)象。

class Test01{

inta=10;//int占4Byte篮绰,以及末尾的對(duì)齊后雷,所以并非空對(duì)象

}

public class Test{

public static void main(String[]args){

Test01 demo01 = new Test01();

Test01 demo02 = null;

? ? }

}

demo01和demo02并非空對(duì)象

public class Test02{

}

Test02為空對(duì)象

空對(duì)象占多少字節(jié)?

開(kāi)啟指針壓縮時(shí):16Byte16B = 8B【Mark Word】 + 4B【類型指針(Klass Pointer) 經(jīng)過(guò)指針壓縮優(yōu)化后變成4B】 + 0B【數(shù)組長(zhǎng)度】+ 0B【實(shí)際數(shù)據(jù)】 + 4B【對(duì)齊】調(diào)優(yōu)參數(shù)(開(kāi)啟指針壓縮參數(shù)):-XX:+UseCompressedOops

關(guān)閉指針壓縮時(shí):16Byte16B = 8B【Mark Word】 + 8B【類型指針(Klass Pointer)】 + 0B【數(shù)組長(zhǎng)度】+ 0B【實(shí)際數(shù)據(jù)】

7.5 普通對(duì)象大小

開(kāi)啟指針壓縮時(shí):32Byte32B= 8B【Mark Word】 + 4B【類型指針(Klass Pointer)】 + 0B【數(shù)組長(zhǎng)度】+ (4B【int大小】+4B【int大小】+8B【double大小】)【實(shí)際數(shù)據(jù)】 + 4B【對(duì)齊】

關(guān)閉指針壓縮時(shí):32Byte32B= 8B【Mark Word】 + 8B【類型指針(Klass Pointer)】 + 0B【數(shù)組長(zhǎng)度】+ (4B【int大小】+4B【int大小】+8B【double大小】)【實(shí)際數(shù)據(jù)】

7.6 數(shù)組對(duì)象大小

如關(guān)閉指針壓縮的圖所示吠各,在沒(méi)有開(kāi)啟指針壓縮的數(shù)組對(duì)象的對(duì)象頭中其實(shí)是存在著對(duì)齊填充的臀突。對(duì)齊大小仍為8B的整數(shù)倍。

8. 對(duì)象指針(OOP)

8.1 壓縮指針是怎么實(shí)現(xiàn)的

前提假設(shè):對(duì)象內(nèi)存從0x00000開(kāi)始贾漏,且連續(xù)候学,中間無(wú)其他數(shù)據(jù)

JVM的實(shí)現(xiàn)方式是 因?yàn)閷?duì)象大小都是8B的倍數(shù),所以存儲(chǔ)的內(nèi)存地址都是以000結(jié)尾的纵散,壓縮時(shí)梳码,可以將末尾的000去掉

如:0x10000 壓縮后——> 0x10

解壓的時(shí)候隐圾,直接在末尾添加三個(gè)0即可

8.2 如何擴(kuò)容OOP?

咋不關(guān)閉指針壓縮的情況下掰茶,如何擴(kuò)容OOP暇藏?

可以可以在地址末尾補(bǔ)0,則OOP的位從35位變成36位濒蒋,支持最大的類空間則變成2^36

為什么JVM不擴(kuò)容叨咖?因?yàn)樽詈笠晃谎a(bǔ)0會(huì)浪費(fèi)空間。

8.3 哪些信息會(huì)被壓縮啊胶?

1.對(duì)象的全局靜態(tài)變量(即類屬性) 2.對(duì)象頭信息:64位平臺(tái)下甸各,原生對(duì)象頭大小為16字節(jié),壓縮后為12字節(jié) 3.對(duì)象的引用類型:64位平臺(tái)下焰坪,引用類型本身大小為8字節(jié)趣倾,壓縮后為4字節(jié) 4.對(duì)象數(shù)組類型:64位平臺(tái)下,數(shù)組類型本身大小為24字節(jié)某饰,壓縮后16字節(jié)

8.4 哪些信息不會(huì)被壓縮儒恋?

1.指向非Heap的對(duì)象指針 2.局部變量、傳參黔漂、返回值诫尽、NULL指針

9. 虛擬機(jī)棧溢出

9.1 導(dǎo)致棧溢出的原因有哪些?

調(diào)用鏈過(guò)長(zhǎng)

死循環(huán)

無(wú)限遞歸

JDK默認(rèn)棧大小是多少炬守?1M

演示棧溢出代碼:(已手動(dòng)設(shè)置JVM棧大小為160K【因?yàn)镴VM最小就是160K牧嫉,不能少于160K】)

若將虛擬機(jī)大小設(shè)置為160k-Xss160k,經(jīng)過(guò)測(cè)試后减途,調(diào)用深度為772

則可計(jì)算出酣藻,棧幀大小為:160*1024 / 772 = 212

9.2 如何避免棧溢出?

在寫(xiě)遞歸/死循環(huán)的時(shí)候鳍置,一定要給遞歸/死循環(huán)一個(gè)出口辽剧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市税产,隨后出現(xiàn)的幾起案子怕轿,更是在濱河造成了極大的恐慌,老刑警劉巖辟拷,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撞羽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡梧兼,警方通過(guò)查閱死者的電腦和手機(jī)放吩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)智听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)羽杰,“玉大人渡紫,你說(shuō)我怎么就攤上這事】既” “怎么了惕澎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)颜骤。 經(jīng)常有香客問(wèn)我唧喉,道長(zhǎng),這世上最難降的妖魔是什么忍抽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任八孝,我火速辦了婚禮,結(jié)果婚禮上鸠项,老公的妹妹穿的比我還像新娘干跛。我一直安慰自己,他們只是感情好祟绊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布楼入。 她就那樣靜靜地躺著,像睡著了一般牧抽。 火紅的嫁衣襯著肌膚如雪嘉熊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天扬舒,我揣著相機(jī)與錄音阐肤,去河邊找鬼。 笑死讲坎,一個(gè)胖子當(dāng)著我的面吹牛泽腮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衣赶,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼诊赊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了府瞄?” 一聲冷哼從身側(cè)響起碧磅,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遵馆,沒(méi)想到半個(gè)月后鲸郊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡货邓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年秆撮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片换况。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡职辨,死狀恐怖盗蟆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舒裤,我是刑警寧澤喳资,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站腾供,受9級(jí)特大地震影響仆邓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伴鳖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一节值、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧榜聂,春花似錦察署、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至休吠,卻和暖如春扳埂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘤礁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柜思,地道東北人岩调。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赡盘,于是被迫代替她去往敵國(guó)和親号枕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容