本篇內(nèi)容介紹了“Java虛擬機經(jīng)典問題有哪些”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)公司是一家專注于成都網(wǎng)站設(shè)計、成都網(wǎng)站制作與策劃設(shè)計,化隆網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)10余年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:化隆等地區(qū)。化隆做網(wǎng)站價格咨詢:028-86922220Java 是如何在保證可移植性的前提下提供高執(zhí)行效率的?
Java 程序最為常見的執(zhí)行方式,是預(yù)先編譯為一種名為 Java 字節(jié)碼的中間代碼格式。這種代碼格式無法直接運行在 CPU 之上,而是需要借助 JVM 來執(zhí)行。換句話說,只要某個平臺提供了合乎 JVM 規(guī)范的實現(xiàn),它便能執(zhí)行這份 Java 字節(jié)碼。這也就是我們經(jīng)常說的“一次編寫,到處運行”。
主流的 OpenJDK/OracleJDK 中所提供的 JVM 叫做 HotSpot。它同時采用了解釋執(zhí)行和即時編譯。解釋執(zhí)行就好比同聲傳譯,JVM 一邊理解輸入的字節(jié)碼一邊向 CPU 發(fā)出指令序列;即時編譯則是“磨刀不誤砍柴工”,JVM 會在運行過程中將熱點代碼編譯成為可直接執(zhí)行的二進(jìn)制代碼。
這種混合執(zhí)行模式是建立在程序符合二八定律的假設(shè)上,即百分之二十的代碼占據(jù)了百分之八十的計算資源。對于不常用代碼,我們無需耗費時間將其編譯成二進(jìn)制代碼,而是采取解釋執(zhí)行的方式運行;另一方面,對于僅占據(jù)小部分的熱點代碼,JVM 則會花費時間將其編譯為二進(jìn)制代碼,以達(dá)到理想的運行效率。
異常捕獲是如何實現(xiàn)的?
在編譯生成的 Java 字節(jié)碼中,每個方法都附帶一個異常表。異常表中的每一行均定義了一條異常執(zhí)行路徑,其中包括規(guī)定捕獲范圍的起始字節(jié)碼索引、終止(不包含)字節(jié)碼索引,異常處理代碼的起始字節(jié)碼索引,以及所捕獲的異常類型。
當(dāng)程序觸發(fā)異常時,JVM 會從上至下遍歷異常表中的所有條目。當(dāng)觸發(fā)異常的字節(jié)碼的索引值在某行異常表條目的捕獲范圍內(nèi),JVM 會判斷所拋出的異常和該條目想要捕獲的異常是否匹配。如果匹配,JVM 會將控制流轉(zhuǎn)移至該條目所指向的異常處理代碼。
上述異常捕獲機制還被用于 finally 從句的實現(xiàn)。通常,Java 程序的編譯器 javac 會復(fù)制多份 finally 代碼塊,放置于生成的 Java 字節(jié)碼之中,然后通過生成多行異常表條目,來實現(xiàn)完整的 finally 邏輯。
反射調(diào)用為什么慢?
默認(rèn)情況下,反射調(diào)用首先會被委派給 native 方法來進(jìn)行??上攵?,其運行效率低下。當(dāng)某個反射調(diào)用的調(diào)用次數(shù)達(dá)到 15 之后,JDK 代碼斷定該調(diào)用屬于熱點調(diào)用。繼而,JDK 將動態(tài)生成直接調(diào)用目標(biāo)方法的字節(jié)碼,并將反射調(diào)用的委派對象由原本的 native 方法實現(xiàn)切換至該動態(tài)生成的實現(xiàn)。這種方式的運行效率相對于 native 方法來說要高很多。
之所以 JDK 不從一開始便采用動態(tài)生成字節(jié)碼的方式,主要是因為生成過程需要耗費一定的時間。對于那些整個生命周期中僅執(zhí)行數(shù)次的反射調(diào)用,動態(tài)生成字節(jié)碼將得不償失。
然而,即便是直接調(diào)用目標(biāo)方法的動態(tài)實現(xiàn),其峰值性能也無法跟真正的直接調(diào)用相媲美。這背后涉及到即時編譯中的虛方法內(nèi)聯(lián)。
相關(guān)文章: < 方法內(nèi)聯(lián)(下)>
垃圾回收的基礎(chǔ)思想是什么?
目前 JVM 的主流垃圾回收器采取的都是可達(dá)性分析算法。該算法的實質(zhì)是將一系列被稱為 GC Roots 的對象作為初始的存活對象合集,然后從該合集出發(fā)探索所有能夠被該集合引用到的對象,并標(biāo)記為存活對象。當(dāng)標(biāo)記階段結(jié)束之后,未被標(biāo)記到的對象便是可以清除的。
傳統(tǒng)的垃圾回收算法在標(biāo)記、清除過程中需要中止其他應(yīng)用線程,即所謂的 Stop-The-World。新型的垃圾回收算法,如 CMS、G1 以及 ZGC,盡可能地實現(xiàn)并發(fā)標(biāo)記、清除,從而讓 Stop-The-World 的時間長度可控。
垃圾回收的另一基礎(chǔ)思想則是分代回收。JVM 會將新生成的對象劃為新生代,而將在多次垃圾回收中存活下來的對象劃為老年代。JVM 會為不同的分代設(shè)置不同的回收算法,從而達(dá)到新生代多收集、快收集,老年代少收集、全收集的目標(biāo)。
如何理解 Java 內(nèi)存模型?
現(xiàn)代計算機多為對稱多處理器的體系架構(gòu)。每個處理器均有獨立的寄存器組和緩存(這在 Java 內(nèi)存模型中被抽象為工作內(nèi)存);多個處理器可同時執(zhí)行同一進(jìn)程中的不同線程。
在 Java 程序中,不同線程可能訪問同一變量或?qū)ο蟆H绻斡删幾g器或處理器對這些訪問進(jìn)行優(yōu)化,則很可能出現(xiàn)在單線程執(zhí)行思維下無法想象的問題。因此,Java 語言規(guī)范引入了 Java 內(nèi)存模型,通過定義多項規(guī)則對編譯器和處理器進(jìn)行限制。
這些規(guī)則所體現(xiàn)的最為重要的屬性便是可見性,即對某一變量的訪問能否被同一線程的其他操作,或者不同線程所觀測到。Java 內(nèi)存模型引入了多種 happens-before 關(guān)系,以實現(xiàn)上述可見性。以 volatile 字段為例,對其的寫操作 happens before 這之后的讀操作,也就是說,我們總能讀到 volatile 字段的最新值。
JVM 如何應(yīng)對對象鎖的各種場景?
重量級鎖是最為基礎(chǔ)、最為低效的對象鎖實現(xiàn)。JVM 會阻塞加鎖失敗的線程,并且在目標(biāo)鎖被釋放的時候,喚醒這些線程。我們用等紅燈作類比。Java 線程進(jìn)入阻塞狀態(tài)相當(dāng)于熄火停車,再次點火啟動必然耗費時間。JVM 會在進(jìn)入阻塞狀態(tài)之前進(jìn)行自旋,也就是怠速停車。如果目標(biāo)鎖能夠在短時間內(nèi)被釋放出來,該線程便能夠不進(jìn)入阻塞狀態(tài),直接獲取該鎖。
重量級鎖針對的是多個線程同時競爭同一把鎖的場景。在現(xiàn)實中,多個線程可能在不同時間段持有同一把鎖。為了應(yīng)對這種沒有鎖競爭的情況,JVM 采用了輕量級鎖機制。在加鎖時,JVM 將在鎖對象處做標(biāo)記,指向當(dāng)前線程的棧上;在解鎖時,上述標(biāo)記會被清除。如果某線程在請求鎖時,發(fā)現(xiàn)該鎖為輕量級鎖,并且指向另一線程所對應(yīng)的棧,那么它會將該鎖膨脹為重量級鎖。
偏向鎖所應(yīng)對的場景則更為樂觀:至始至終只有一個線程請求某把鎖。JVM 采取的做法是在第一次加鎖時為鎖對象做標(biāo)記,使其指向當(dāng)前線程的地址;在解鎖時則不做任何操作。如果下一次請求該鎖的仍是同一線程,便直接跳過標(biāo)記過程;否則,JVM 會將該鎖膨脹為輕量級鎖。
“Java虛擬機經(jīng)典問題有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!