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