圖文故事:一文帶你走進(jìn)JVM的世界
最近的業(yè)余時(shí)間基本都在寫一個(gè)簡(jiǎn)單的RPC框架(附帶詳解以及知識(shí)點(diǎn)講解那種,爭(zhēng)取新手都能看懂),所以原創(chuàng)文耽擱了一些,不過(guò)也可以讓你們緩沖一下,前今天發(fā)了一些不錯(cuò)的原創(chuàng)技術(shù)文章。加油!奧利給!❞
小強(qiáng)是一個(gè)工作3年有余的開(kāi)發(fā)工程師,從他的發(fā)量你就可以知道,小強(qiáng)資歷還尚淺。
程序員驚人發(fā)量
最近公司沒(méi)什么事,他也開(kāi)始無(wú)聊起來(lái)了。這天下午,同事們?cè)诩ち业挠懻撨@業(yè)務(wù),但他沒(méi)有參與,于是他決定學(xué)習(xí)些什么知識(shí),無(wú)聊的翻著各個(gè)網(wǎng)頁(yè),發(fā)現(xiàn)JVM是各位大神們推薦過(guò)的知識(shí),于是決定好好看一看。
5分鐘過(guò)后……
小強(qiáng)感到這知識(shí)有些枯燥乏味,怪不得是大神們能看的!又看了幾分鐘,小強(qiáng)倦意襲來(lái),揉了揉睡眼惺忪的眼睛。
然而就在這一刻,他突然發(fā)現(xiàn)周圍同事激烈討論的聲音聽(tīng)不到了,安靜到了極致。
1. 入界
小強(qiáng)努力的睜開(kāi)眼睛,才發(fā)現(xiàn)自己竟然身處一個(gè)白茫茫的空間中,嚇得一跳,心想我這是怎么了,穿越了?但穿越也得穿越到一個(gè)人間如畫,美女如云的地方啊……,這境地……
突然前方走來(lái)一個(gè)白胡子老頭,小強(qiáng)正想開(kāi)口,老頭捷足先登:你好,我是這個(gè)JVM世界的締造者,你可以叫我 “HotSpot”,不過(guò)這無(wú)所謂,因?yàn)槲宜鶆?chuàng)造的這個(gè)世界,是按照 “JVM規(guī)范” 來(lái)完成的。我正在休息時(shí),發(fā)現(xiàn)來(lái)了一位客人,原來(lái)是你。
小強(qiáng):我是想問(wèn)……
老頭:不用問(wèn),我知道,你是想了解一下我創(chuàng)造的這個(gè)世界吧!跟我來(lái)吧。
這老頭,我還沒(méi)說(shuō)話,這就結(jié)束了!好吧,跟你看看且說(shuō)。
老頭邊走邊道:JVM 的世界 空間是有限的,我們堅(jiān)持一個(gè)原則 : 各司其職,不留無(wú)用之人!
小強(qiáng):啊!好殘酷。
老頭:不,這不是殘酷,我們這個(gè)世界生來(lái)就是為客戶提供服務(wù),為客戶發(fā)光發(fā)熱的,每個(gè)人奉獻(xiàn)出了自己的能力就是圓滿完成任務(wù),退出舞臺(tái)是理所應(yīng)當(dāng)?shù)?,也是他們最好的歸宿。
小強(qiáng):也是,這樣這個(gè)世界才不會(huì)那么擁擠,大家才能井然有序的工作,我怎么這么不開(kāi)竅呢……
2. 布局
老頭前面停了下來(lái):過(guò)來(lái),帶你先看看我們世界的整體組成和中心區(qū)如何布局。

整體布局圖
先來(lái)看看我們最主要的日常工作區(qū)(運(yùn)行時(shí)數(shù)據(jù)區(qū)),為了讓我們工作起來(lái)更有效率,我們將世界空間劃分為這幾個(gè)板塊。
「居住區(qū)-堆」
這里是人們工作外的居住區(qū),居住區(qū)我們基于人們的年齡也進(jìn)一步分出了,伊甸區(qū),幸存者區(qū),老年區(qū)。

居住區(qū)
「工作區(qū)-?!?/h3>
每個(gè)任務(wù)來(lái)臨時(shí),都會(huì)在工作區(qū)單獨(dú)開(kāi)辟出一個(gè)地方來(lái)用于完成這個(gè)任務(wù)。

棧幀圖
「記錄者-程序計(jì)數(shù)器」
由于我們同時(shí)能做的任務(wù)有限,所以我們需要為不同的任務(wù)劃分出不同的時(shí)間片,我們?cè)谇袚Q任務(wù)的時(shí)候,需要一個(gè)記錄者,能夠記錄我們這個(gè)任務(wù)做到了哪里,下次回來(lái)能夠繼續(xù)做。
「?jìng)}庫(kù)管理區(qū)-方法區(qū)」
這里存放著工人的模板以及常用的不變的工具等。
3. 生與死
這里工作的人們都會(huì)經(jīng)歷生與死,大部分人們活不到老年,但這不重要,重要的是他為我們做出了貢獻(xiàn)。
3.1 出生
老頭:這里的每個(gè)人都有一個(gè)模板(類),看到那個(gè)正在居住區(qū)休息的高個(gè)嗎?他叫張三,他是根據(jù)外部客戶給定的模板 “ User Class” 創(chuàng)造的,他可是客戶最喜愛(ài)的工人了。你知道客戶的這些模板(類)是如何進(jìn)入的到我們的世界中的嗎?
小強(qiáng):這個(gè)我知道點(diǎn),之前看過(guò)一點(diǎn)點(diǎn)。這個(gè)過(guò)程還是有些復(fù)雜的,客戶的模板(類)是通過(guò)一個(gè)翻譯工廠(編譯器) 將它翻譯成class 字節(jié)碼,因?yàn)槟銈冞@個(gè)世界只認(rèn)識(shí)字節(jié)碼,然后有你們的加載系統(tǒng)將它們加載到這里。
加載過(guò)程中有這些階段:

類加載過(guò)程
其中加載階段是由加載器來(lái)完成的。
老頭:是的,我們提供了三種加載器,啟動(dòng)類加載器,擴(kuò)展類加載器,應(yīng)用類加載器,當(dāng)然客戶也可以自定義加載器。

雙親委派模型
小強(qiáng):他們遵循著雙親委派模型,但是我一直不太理解這個(gè)詞!
老頭:這是由于你們語(yǔ)言翻譯的問(wèn)題導(dǎo)致,這個(gè)模式叫 “parents delegation”,知道了吧!它是指有你的父輩們來(lái)幫你完成。
小強(qiáng):那雙親委派模式 有什么好處呢?
老頭:
具有優(yōu)先級(jí)層次的關(guān)系可以避免模板(類)的重復(fù)加載
安全考慮可以防止Java核心api被替換
老頭繼續(xù)道:那連接過(guò)程中的三步,你知道是做什么嗎?
小強(qiáng):具體的我就不知道了哎……
老頭笑了笑:對(duì)于客戶定義的模板(類),我們可不是來(lái)者不拒的,為了我們這個(gè)世界的安全以及能提供更好的服務(wù),我們會(huì)對(duì)模板做一些驗(yàn)證及后續(xù)操作。
驗(yàn)證包括格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證,符號(hào)驗(yàn)證。當(dāng)驗(yàn)證通過(guò)后,我們會(huì)為模板所依賴的東西(類變量)分配空間,最后將符號(hào)引用替換為直接引用。
老頭看了看小強(qiáng)眉頭緊皺,于是繼續(xù)補(bǔ)充:你可能不了解什么是符號(hào)引用和直接引用!
符號(hào)引用就是在編譯時(shí),并不知道模板(類)所依賴的其他東西,會(huì)在我們的空間中的哪個(gè)位置,只能用符號(hào)來(lái)表示。
直接引用就是 所有東西被加載到這里后會(huì)有自己的真實(shí)空間地址,然后去替換符號(hào)引用。這樣運(yùn)行時(shí)就能找到它們所依賴的東西了。
最后就是初始化了,這個(gè)階段主要是對(duì)類變量初始化,是執(zhí)行類構(gòu)造器的過(guò)程。
小強(qiáng):我怎么沒(méi)看到這些模板呢?
老頭:這些模板我把他們隱藏在世界的后方,大多數(shù)人是見(jiàn)不到的,他們統(tǒng)稱為 Klass。
小強(qiáng):不對(duì)啊!你是不是搞錯(cuò)了?不應(yīng)該叫 Class嗎?
老頭:哈哈!我剛才說(shuō)了,大多數(shù)人見(jiàn)不到,你就是其中之一啊!你們平時(shí)見(jiàn)到的 Class只是對(duì) Klass的一種封裝而已,真正記錄模板中的具體元信息的就是 Klass。這回要記住了,年輕人。
3.2 工人
小強(qiáng):為什么你的工人是等量差的身高呢?
對(duì)象長(zhǎng)度
老頭:你的觀察還是挺仔細(xì)的嘛!是的,他們確實(shí)是等量差的,想要知道為什么,要先了解這些工人有哪些部分組成。

對(duì)象的組成
它們頭部大小是固定的,身體大小是由自己的屬性數(shù)據(jù)決定的,而最后的腳部卻是我來(lái)決定的,如果前面兩個(gè)數(shù)據(jù)的大小沒(méi)有達(dá)到 8 的倍數(shù),那么我就會(huì)來(lái)填充,所以就是這里的填充使得他們擁有了等量的身高差(內(nèi)存對(duì)齊)。
我是基于兩點(diǎn)原因來(lái)這個(gè)締造他們的:
平臺(tái)原因:不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
性能原因:中央大腦(CPU)訪問(wèn)內(nèi)存是有內(nèi)存訪問(wèn)粒度的,就是每次訪問(wèn)內(nèi)存的長(zhǎng)度是固定的,如果不這樣做,那么中央大腦起需要訪問(wèn)兩次內(nèi)存,而對(duì)齊后只需要一次。
小強(qiáng):嗯,明白了!那能給我說(shuō)說(shuō)這些工人在居住區(qū)為什么要不斷的搬遷呢?
3.3 成長(zhǎng)
老頭:經(jīng)過(guò)長(zhǎng)時(shí)間的觀察,我發(fā)現(xiàn)每個(gè)工人的生命長(zhǎng)短是不一樣的。所以我把居住區(qū)分為新生代,老年代,然后讓他們合理的搬遷,這樣能有效的利用空間而且讓垃圾小分隊(duì)工作更有效率。

堆區(qū)分代
工人誕生后會(huì)分配到Eden區(qū),當(dāng)Eden區(qū)人員快滿時(shí),垃圾小分隊(duì)會(huì)來(lái)清掃,清掃后如果工人還活著,那么他們將搬遷至Survivor區(qū)中的其中一個(gè),當(dāng)這個(gè)Survivor快滿時(shí),垃圾小分隊(duì)會(huì)將還活著的工人搬遷至另一個(gè)Survivor區(qū)中,就這樣重復(fù)著,每經(jīng)歷一次垃圾小分隊(duì)的清掃,活著的工人就會(huì)長(zhǎng)大一歲,直到工人的年齡達(dá)到15歲,到達(dá)后會(huì)將他們搬遷至老年代生活的地方。但也有例外,如果某個(gè)工人吃的太胖,新生代容不下他,那么他將直接去老年代住下。當(dāng)老年代快住滿時(shí),將會(huì)有垃圾大掃除(full gc)。
小強(qiáng):原來(lái)如此啊!從此我再也不是只知道堆區(qū)棧區(qū)的菜鳥(niǎo)啦!哈哈哈哈……
老頭:小伙子,不要高興太早!你到目前為止所了解的仍是九牛一毛。
3.4 死亡證明
小強(qiáng):如何確定工人是否到達(dá)生命的盡頭呢?
第一種:引用計(jì)數(shù)法
給每個(gè)工人添加一個(gè)引用計(jì)數(shù)器,就是只要有人需要這個(gè)工人幫忙,那么就給這個(gè)工人的計(jì)數(shù)加1,反之,別人不再需要這個(gè)工人的幫忙,那么計(jì)數(shù)就減1,直到這個(gè)計(jì)數(shù)為0,那么表示這個(gè)工人生命到了盡頭。
但這種方法有個(gè)問(wèn)題:如果A工人和B工人相互需要幫忙,但沒(méi)有任何其他工人或任務(wù)需要他們兩個(gè),那么他們兩個(gè)會(huì)永遠(yuǎn)活下!「所以這種方法我們不會(huì)采取的。」
第二種:可達(dá)性分析法
我們找出被稱為 “GC roots”的工人作為起點(diǎn),依次尋找他們工作中依賴的工人,這就可以知道哪些工人是沒(méi)有必要在存在下去了。
小強(qiáng):我怎么知道哪些是 “GC roots”工人呢?
老頭:
工作區(qū)(棧)中的需要用到的工人
倉(cāng)庫(kù)(方法區(qū))中模板(類)本身需要的工人(靜態(tài),常量)
世界后方(native方法)需要的工人
小強(qiáng):Got it!
4. 回收
老頭:下面我?guī)闳フJ(rèn)識(shí)一下垃圾小分隊(duì)的人物吧!不過(guò)在認(rèn)識(shí)他們之前你最好了解一下,垃圾清除的基本方法論。
4.1 基本方法論
收集垃圾遵循的基本方法論有以下幾種:
- 標(biāo)記-清除首先標(biāo)記出所有需要回收的工人(對(duì)象),在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的工人。但這個(gè)有兩個(gè)缺點(diǎn):1. 效率不高 2. 會(huì)產(chǎn)生許多碎片空間
- 復(fù)制將可用的空間一分為二,每次只使用其中一塊,當(dāng)快使用完時(shí),小分隊(duì)回收,然后將活著的工人搬遷至另一塊。這雖然解決了標(biāo)記-清除的效率問(wèn)題,但此種方法卻縮小了一半空間。
- 標(biāo)記-整理首先標(biāo)記出所有需要回收的工人(對(duì)象),然后將存活的工人移動(dòng)到空間的一端,然后清理掉邊界以外的工人。
小強(qiáng)笑了笑:原來(lái)是這三種算法啊!我知道!
老頭:既然知道,那跟我來(lái)認(rèn)識(shí)一下垃圾清掃隊(duì)的人吧!
4.2 主要成員
垃圾清掃隊(duì)有好幾個(gè)小隊(duì)組成,客戶喜歡哪個(gè)小隊(duì)可以指定讓誰(shuí)來(lái)工作,他們各個(gè)隊(duì)伍的清掃方式各不相同也各有優(yōu)劣。
我給你介紹一下兩個(gè)主要成員吧,CMS,G1兩個(gè)小隊(duì)出列。
CMS:到,我們是CMS分隊(duì),全稱叫 “Concurrent Mark Sweep”,顧名思義,我們是采用標(biāo)記清除算法的并發(fā)小分隊(duì),我們以獲取最短回收停頓時(shí)間為目標(biāo)。
小強(qiáng):那你說(shuō)說(shuō)你們是如何工作的?
CMS:我們主要分四個(gè)步驟工作,1. 初始標(biāo)記 2.并發(fā)標(biāo)記 3.重新標(biāo)記 4.并發(fā)清除
小強(qiáng):算啦,這么多步驟太需要時(shí)間來(lái)了解了,我現(xiàn)在知道你的優(yōu)點(diǎn)了,那你的缺點(diǎn)有什么呢?
CMS:這怎么還帶揭人傷疤的……
老頭這時(shí)嚴(yán)肅的咳嗽了兩聲,其意CMS立馬捕獲到了,委屈的說(shuō):
我有三個(gè)缺點(diǎn):
- 當(dāng)資源不是很充足時(shí),占用過(guò)多的資源,導(dǎo)致任務(wù)變慢
- 無(wú)法處理浮動(dòng)垃圾,我們清理的時(shí)候,工人同時(shí)也在工作,我們標(biāo)記后,正好有些工人不在需要了
- 我們分隊(duì)遵循的是“標(biāo)記-清除”算法,所以會(huì)產(chǎn)生大量碎片空間,導(dǎo)致世界大掃除(full gc)提前到來(lái)
心直口快的小強(qiáng)來(lái)了句:原來(lái)你的問(wèn)題這么嚴(yán)重,老頭竟然沒(méi)把你們小分隊(duì)辭掉……
CMS:你…… 想當(dāng)年我們分隊(duì)可是紅極一時(shí)的……
那么我猜G1是不是可以彌補(bǔ)CMS的不足呢?
G1: 說(shuō)實(shí)話,我們分隊(duì)的目標(biāo)就是替換CMS分隊(duì)…… (JDK14 CMS正式落下帷幕)
小強(qiáng)不懷好意的笑了起來(lái),哈哈……,CMS翻著白眼躲到一旁的角落暗自傷感去了。
CMS角落哭泣
小強(qiáng):那G1說(shuō)說(shuō)你的能耐吧!
G1: 我們隊(duì)是基于標(biāo)記整理算法的,因此不會(huì)產(chǎn)生大量碎片空間
- 我們同時(shí)引入了分區(qū)的思路,弱化了分代的概念
- 我們的停頓時(shí)間是可控的,可避免雪崩現(xiàn)象
- 我們也能充分利用客戶給我們的資源,減少停頓時(shí)間
這是我們隊(duì)的優(yōu)勢(shì),接下來(lái)我給你詳細(xì)介紹下我們隊(duì)的情況……
小強(qiáng):好的!你繼續(xù)……
回歸
就在小強(qiáng)聽(tīng)的興趣濃濃時(shí),天空中突然出現(xiàn)一只巨大無(wú)比的手向他襲來(lái),小強(qiáng)躲閃不開(kāi),啊……
一只大巴掌
小強(qiáng)捂著自己的頭,有點(diǎn)恍惚,抬頭一看,擦,技術(shù)總監(jiān)……你怎么也在這?
總監(jiān):我不在這我在哪?在家睡大覺(jué)嗎!
這時(shí)小強(qiáng)才回過(guò)神來(lái),原來(lái)自己還在辦公室,大事不妙啊!
總監(jiān):小強(qiáng),回家多爽,明天就不用來(lái)了吧!
小強(qiáng)一慌,腦袋靈機(jī)一動(dòng):總監(jiān),知道我剛才在做什么嗎?那可不是在睡覺(jué),我有一個(gè)故事你且聽(tīng)聽(tīng)再做決定。
吧啦吧啦……
如果你覺(jué)得本文有不巧當(dāng)之處,請(qǐng)留言告知,如果喜歡本文給個(gè)贊鼓勵(lì)一下。