作者 | 鄭雨迪
來源 | 極客時間《深入拆解 Java 虛擬機(jī)》
作為一位 Java 程序員,在盡情享受 Java 虛擬機(jī)帶來好處的同時洁灵,我們還應(yīng)該去了解和思考“這些技術(shù)特性是如何實現(xiàn)的”浮还,去了解最底層的原理咕幻。只有熟悉 JVM滨攻,你才能在遇到 OutOfMemory 等異常時,不會束手無策饶唤,不會一臉懵逼地上網(wǎng)找解決辦法徐伐,最后就算改了幾個啟動參數(shù)解決了問題,也還是云里霧里募狂。
這次呵晨,我會從我專欄里提取了學(xué)習(xí) Java 虛擬機(jī)的 X 大知識要點,助力大家深入理解 JVM熬尺,知其然也知其所以然。 不過你在看知識點之前谓罗,最好能問問自己你會怎么回答粱哼,再和我提供的內(nèi)容做對比,這樣子提升會比較明顯檩咱。
第一大知識要點:Java 字節(jié)碼是如何在虛擬機(jī)里運行的揭措?
我將以 HotSpot 虛擬機(jī)為例,從虛擬機(jī)以及底層硬件兩個角度刻蚯,來分享解析绊含。
1、從虛擬機(jī)視角來看
執(zhí)行 Java 代碼首先需要將它編譯而成的 class 文件加載到 Java 虛擬機(jī)中炊汹。加載后的 Java 類會被存放于方法區(qū)中躬充。實際運行時,虛擬機(jī)會執(zhí)行方法區(qū)內(nèi)的代碼讨便。
如果你熟悉 X86 的話充甚,你會發(fā)現(xiàn)這和段式內(nèi)存管理中的代碼段類似。而且霸褒,Java 虛擬機(jī)同樣也在內(nèi)存中劃分出堆和棧來存儲運行時數(shù)據(jù)伴找。不同的是,Java 虛擬機(jī)會將棧細(xì)分為面向 Java 方法的 Java 方法棧废菱,面向用 C++ 寫的 native 方法的本地方法棧技矮,以及存放各個線程執(zhí)行位置的 PC 寄存器。
在運行過程中殊轴,每當(dāng)調(diào)用進(jìn)入一個 Java 方法衰倦,Java 虛擬機(jī)會在當(dāng)前線程的 Java 方法棧中生成一個棧幀,用以存放局部變量以及字節(jié)碼的操作數(shù)旁理。這個棧幀的大小是提前計算好的耿币,而且 Java 虛擬機(jī)不要求棧幀在內(nèi)存空間里連續(xù)分布。
當(dāng)退出當(dāng)前執(zhí)行的方法時韧拒,不管是正常返回還是異常返回淹接,Java 虛擬機(jī)均會彈出當(dāng)前線程的當(dāng)前棧幀十性,并將之舍棄。
2塑悼、從硬件視角來看
Java 字節(jié)碼無法直接執(zhí)行劲适。因此,Java 虛擬機(jī)需要將字節(jié)碼翻譯成機(jī)器碼厢蒜。
在 HotSpot 里面霞势,上述翻譯過程有兩種形式:第一種是解釋執(zhí)行,相當(dāng)于同聲傳譯斑鸦,即每解析一條字節(jié)碼愕贡,便翻譯成機(jī)器碼并執(zhí)行;第二種是即時編譯(Just-In-Time compilation巷屿,JIT),則相當(dāng)于線下翻譯,即將整個方法中所包含的字節(jié)碼統(tǒng)一翻譯成機(jī)器碼后在執(zhí)行篙螟。
前者的優(yōu)勢在于無需等待編譯墅冷,而后者的優(yōu)勢在于實際運行速度更快。HotSpot 默認(rèn)采用混合模式腔彰,綜合了解釋執(zhí)行和即時編譯兩者的優(yōu)點。它會先解釋執(zhí)行字節(jié)碼,而后將其中反復(fù)執(zhí)行的熱點代碼端逼,以方法為單位進(jìn)行即時編譯。
第二大知識要點:Java 虛擬機(jī)是如何加載 Java 類的盐欺?
Java 虛擬機(jī)加載 Java 類的過程可分為加載析二、鏈接以及初始化三大步驟。
加載是指查找字節(jié)流,并且據(jù)此創(chuàng)建類的過程。加載需要借助類加載器,在 Java 虛擬機(jī)中艾岂,類加載器使用了雙親委派模型,即接收到加載請求時氓辣,會先將請求轉(zhuǎn)發(fā)給父類加載器。
鏈接,是指將創(chuàng)建成的類合并至 Java 虛擬機(jī)中,使之能夠執(zhí)行的過程。鏈接還分驗證、準(zhǔn)備和解析三個階段,分別完成“驗證被加載類是否滿足 Java 虛擬機(jī)約束”赏寇,“為被加載類靜態(tài)字段分配內(nèi)存”渠退,以及“將被加載類中的符號引用解析成為實際引用”的工作碎乃。其中,Java 虛擬機(jī)規(guī)范并不要求解析階段一定要在鏈接步驟中完成惠奸。
初始化梅誓,則是為標(biāo)記為常量值的字段賦值,以及執(zhí)行 <clinit> 方法的過程佛南。類的初始化僅會被執(zhí)行一次证九,這個特性被用來實現(xiàn)單例的延遲初始化。
第三大知識要點:Java 虛擬機(jī)是如何進(jìn)行垃圾回收的共虑?
Java 虛擬機(jī)中的垃圾回收器采用可達(dá)性分析來探索所有存活的對象愧怜。它從一系列 GC Roots 出發(fā),邊標(biāo)記邊探索所有被引用的對象妈拌。為了防止在標(biāo)記過程中堆棧的狀態(tài)發(fā)生改變拥坛,Java 虛擬機(jī)采取安全點機(jī)制來實現(xiàn) Stop-The-World 操作蓬蝶,暫停其他非垃圾回收線程。
回收垃圾對象的內(nèi)存共有三種基礎(chǔ)算法猜惋,分別為:會造成內(nèi)存碎片的清除算法丸氛、性能開銷較大的壓縮算法、以及堆使用效率較低的復(fù)制算法著摔。
通常來說缓窜,Java 虛擬機(jī)會采用分代回收的思想,將堆劃分為新生代和老年代谍咆,并且通過在不同代中應(yīng)用不同的垃圾回收算法禾锤。
傳統(tǒng)的做法是將新生代再劃分為 Eden 區(qū)和兩個大小一致的 Survivor 區(qū)。在只針對新生代的 Minor GC 中摹察,Eden 區(qū)和非空 Survivor 區(qū)的存活對象會被復(fù)制到空的 Survivor 區(qū)中恩掷,當(dāng) Survivor 區(qū)中的存活對象復(fù)制次數(shù)超過一定數(shù)值時,它將被晉升至老年代供嚎。
因為 Minor GC 只針對新生代進(jìn)行垃圾回收黄娘,所以在枚舉 GC Roots 的時候,它需要考慮從老年代到新生代的引用克滴。為了避免掃描整個老年代逼争,Java 虛擬機(jī)引入了名為卡表的技術(shù),大致地標(biāo)出可能存在老年代到新生代的引用的內(nèi)存區(qū)域劝赔。
G1 垃圾回收器將堆劃分為多個等大的區(qū)域誓焦,每個區(qū)域都可以充當(dāng) Eden 區(qū),Survivor 區(qū)或者老年代區(qū)望忆。G1 會優(yōu)先收集垃圾最多的區(qū)域罩阵,從而最大化垃圾回收的效益竿秆。這也是 Garbage First 名字的由來启摄。
Java 11 中引入的實驗性垃圾回收器 ZGC,僅在掃描 GC Roots 時請求 Stop-The-World幽钢,暫停應(yīng)用線程歉备。因此,它宣稱可將 GC 暫停時間控制在 10ms 以下匪燕。ZGC 暫時沒有應(yīng)用分代回收的思路蕾羊,將整個堆空間看成一塊,其代價是垃圾回收 CPU 消耗較高帽驯。
第四大知識要點:Java 內(nèi)存模型是什么龟再?
在現(xiàn)代計算機(jī)系統(tǒng)中,代碼通常不會按照書寫順序執(zhí)行尼变。造成這一情況的原因有三個利凑,分別為編譯器的重排序浆劲,處理器的亂序執(zhí)行,以及內(nèi)存系統(tǒng)的重排序哀澈。
以內(nèi)存系統(tǒng)重排序為例牌借,在多處理器體系架構(gòu)下,每個處理器都可能緩存了一部分?jǐn)?shù)據(jù)割按。由于時刻保持緩存數(shù)據(jù)與內(nèi)存數(shù)據(jù)同步的性能代價太大膨报,因此部分體系架構(gòu)可能允許緩存數(shù)據(jù)與內(nèi)存數(shù)據(jù)不同步。這對 Java 程序的影響便是适荣,兩個不同的 Java 線程在同一時間內(nèi)看到的同一塊內(nèi)存地址中的值可能不同现柠。
Java 內(nèi)存模型是針對上述問題而提出的一套規(guī)范,用以允許 Java 程序員更為細(xì)致地定義 Java 程序的內(nèi)存行為束凑。它通過定義了一系列的 happens-before 操作晒旅,讓應(yīng)用程序開發(fā)者能夠輕易地表達(dá)不同線程的操作之間的內(nèi)存可見性。
在遵守 Java 內(nèi)存模型的前提下汪诉,即時編譯器以及底層體系架構(gòu)能夠調(diào)整內(nèi)存訪問操作废恋,以達(dá)到性能優(yōu)化的效果。如果開發(fā)者沒有正確地利用 happens-before 規(guī)則扒寄,那么將可能導(dǎo)致數(shù)據(jù)競爭鱼鼓。
Java 內(nèi)存模型是通過內(nèi)存屏障來禁止重排序的。對于即時編譯器來說该编,內(nèi)存屏障將限制它所能做的重排序優(yōu)化迄本。對于處理器來說,內(nèi)存屏障會導(dǎo)致緩存的刷新操作课竣。
擴(kuò)展閱讀
我的專欄《深入拆解 Java 虛擬機(jī)》已完結(jié)嘉赎,非常感謝在我專欄完結(jié)之前的 16000 多名訂閱用戶,在未了解完整內(nèi)容的情況下于樟,毅然訂閱了我的專欄公条。為不辜負(fù)大家的信任,我?guī)缀趺科獙诙紩罅块喿x HotSpot 的源代碼迂曲,和同事討論實現(xiàn)背后的設(shè)計理念靶橱,在這個過程中,我也發(fā)現(xiàn)了一些 HotSpot 中的 Bug路捧,或年久失修的代碼关霸,又或者是設(shè)計不合理的地方,這大概算作寫專欄和我本職工作重疊的地方吧杰扫。
專欄雖然到此已經(jīng)結(jié)束了队寇,但是并不代表你對 Java 虛擬機(jī)學(xué)習(xí)的停止。我想章姓,專欄的內(nèi)容僅僅是為你打開了 JVM 學(xué)習(xí)的大門佳遣,里面的風(fēng)景炭序,還是需要你自己來探索。在文章的后面苍日,我列出了一系列的 Java 虛擬機(jī)技術(shù)的相關(guān)博客和閱讀資料惭聂,你仍然可以繼續(xù)加餐。
你可以關(guān)注國內(nèi)幾位 Java 虛擬機(jī)大咖的知乎或微信公眾號:
-
R 大相恃,個人認(rèn)為是中文圈子里最了解 Java 虛擬機(jī)設(shè)計實現(xiàn)的人辜纲,你可以關(guān)注他的知乎賬號:
你假笨,原阿里 Java 虛擬機(jī)團(tuán)隊成員拦耐,現(xiàn) PerfMa CEO耕腾,公眾號:你假笨
江南白衣,唯品會資深架構(gòu)師杀糯,公眾號:春天的旁邊扫俺;
占小狼,美團(tuán)基礎(chǔ)架構(gòu)部技術(shù)專家固翰,公眾號:占小狼的博客
楊曉峰狼纬,前甲骨文首席工程師,公眾號:小肥羊聊 Java骂际。
如果英文閱讀沒問題的話疗琉,你可以關(guān)注:
-
Cliff Click
-
Aleksey Shipil?v
-
Nitsan Wakart
你也可以關(guān)注
-
Java Virtual Machine Language Submit
-
Oracle Code One
關(guān)于 Java 虛擬機(jī)的演講,以便掌握 Java 的最新發(fā)展動向歉铝。
當(dāng)然盈简,如果對 GraalVM 感興趣的話,你可以訂閱我們團(tuán)隊的博客太示,之后我會考慮逐一進(jìn)行翻譯
至于其他閱讀材料柠贤,你可以參考 R 大的這份書單
https://www.douban.com/doulist/2545443/
或者這個匯總貼
https://github.com/deephacks/awesome-jvm
如果本專欄已經(jīng)激發(fā)了你對 Java 虛擬機(jī)的學(xué)習(xí)熱情,那么我建議你著手閱讀 HotSpot 源代碼类缤,并且回饋 OpenJDK 開源社區(qū)臼勉。這種回饋并不一定是提交 patch,也可以是 bug report 或者改進(jìn)建議等等呀非。
道阻且長坚俗,努力加餐~!
可以說速缆,Java 虛擬機(jī)就是每一位 Java 工程師進(jìn)階加薪的利器艺糜,你想往上升,你想深入技術(shù)尉剩,不想一直停留在簡單開發(fā)毅臊,或者你在做 Java 性能分析管嬉、調(diào)優(yōu)工作時,那么础倍,Java 虛擬機(jī)絕對是一把助力的利劍沟启。