Java虚拟机必学之四大知识要点,附学习资料
轉(zhuǎn)載自??Java虛擬機(jī)必學(xué)之四大知識要點(diǎn),附學(xué)習(xí)資料
作為一位 Java 程序員,在盡情享受 Java 虛擬機(jī)帶來好處的同時,我們還應(yīng)該去了解和思考“這些技術(shù)特性是如何實(shí)現(xiàn)的”,去了解最底層的原理。只有熟悉 JVM,你才能在遇到 OutOfMemory 等異常時,不會束手無策,不會一臉懵逼地上網(wǎng)找解決辦法,最后就算改了幾個啟動參數(shù)解決了問題,也還是云里霧里。
這次,我會從我專欄里提取了學(xué)習(xí) Java 虛擬機(jī)的 X 大知識要點(diǎn),助力大家深入理解 JVM,知其然也知其所以然。 不過你在看知識點(diǎn)之前,最好能問問自己你會怎么回答,再和我提供的內(nèi)容做對比,這樣子提升會比較明顯。
?第一大知識要點(diǎn):Java 字節(jié)碼是如何在虛擬機(jī)里運(yùn)行的?
我將以 HotSpot 虛擬機(jī)為例,從虛擬機(jī)以及底層硬件兩個角度,來分享解析。
1、從虛擬機(jī)視角來看
執(zhí)行 Java 代碼首先需要將它編譯而成的 class 文件加載到 Java 虛擬機(jī)中。加載后的 Java 類會被存放于方法區(qū)中。實(shí)際運(yùn)行時,虛擬機(jī)會執(zhí)行方法區(qū)內(nèi)的代碼。
如果你熟悉 X86 的話,你會發(fā)現(xiàn)這和段式內(nèi)存管理中的代碼段類似。而且,Java 虛擬機(jī)同樣也在內(nèi)存中劃分出堆和棧來存儲運(yùn)行時數(shù)據(jù)。不同的是,Java 虛擬機(jī)會將棧細(xì)分為面向 Java 方法的 Java 方法棧,面向用 C++ 寫的 native 方法的本地方法棧,以及存放各個線程執(zhí)行位置的 PC 寄存器。
?
在運(yùn)行過程中,每當(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)勢在于實(shí)際運(yùn)行速度更快。HotSpot 默認(rèn)采用混合模式,綜合了解釋執(zhí)行和即時編譯兩者的優(yōu)點(diǎn)。它會先解釋執(zhí)行字節(jié)碼,而后將其中反復(fù)執(zhí)行的熱點(diǎn)代碼,以方法為單位進(jìn)行即時編譯。
?第二大知識要點(diǎ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)存”,以及“將被加載類中的符號引用解析成為實(shí)際引用”的工作。其中,Java 虛擬機(jī)規(guī)范并不要求解析階段一定要在鏈接步驟中完成。
初始化,則是為標(biāo)記為常量值的字段賦值,以及執(zhí)行 <clinit> 方法的過程。類的初始化僅會被執(zhí)行一次,這個特性被用來實(shí)現(xiàn)單例的延遲初始化。
?
?第三大知識要點(diǎn):Java 虛擬機(jī)是如何進(jìn)行垃圾回收的?
Java 虛擬機(jī)中的垃圾回收器采用可達(dá)性分析來探索所有存活的對象。它從一系列 GC Roots 出發(fā),邊標(biāo)記邊探索所有被引用的對象。為了防止在標(biāo)記過程中堆棧的狀態(tài)發(fā)生改變,Java 虛擬機(jī)采取安全點(diǎn)機(jī)制來實(shí)現(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 中引入的實(shí)驗性垃圾回收器 ZGC,僅在掃描 GC Roots 時請求 Stop-The-World,暫停應(yīng)用線程。因此,它宣稱可將 GC 暫停時間控制在 10ms 以下。ZGC 暫時沒有應(yīng)用分代回收的思路,將整個堆空間看成一塊,其代價是垃圾回收 CPU 消耗較高。
?第四大知識要點(diǎn):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)致緩存的刷新操作。
總結(jié)
以上是生活随笔為你收集整理的Java虚拟机必学之四大知识要点,附学习资料的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图解elasticsearch原理转载自
- 下一篇: 广西艺术学院相思湖校区具体位置