形象談JVM-第三章-即時編譯器優化技術

来源:https://www.cnblogs.com/xingxiangtan/archive/2023/08/17/17635383.html
-Advertisement-
Play Games

**即時編譯器優化技術一覽:** ![](https://img2023.cnblogs.com/blog/3256961/202308/3256961-20230816153001309-163136082.png) ![](https://img2023.cnblogs.com/blog/325 ...


即時編譯器優化技術一覽:

相信許多同學看完這個表格,腦子裡面嗡嗡的,這些名字也是晦澀難懂,要實現這些優化的技術確實有比較大的難度,但是咱們只是學習,去理解這些技術,其實並不難,下麵咱們直接開講。

首先需要明確一點的,作者是為了講解方便,使用java的語法來表示優化技術所發揮出來的作用,實際上編譯優化並不是建立在java代碼之上的,而是建立在代碼的中間表示或者是機器碼之上的。

優化前:

優化後:

相信很容易看到優化後的不一樣,將get()直接優化成了.value,這個叫做方法內聯

它的主要目的有兩個:

一是去除方法調用的成本(如查找方法版本、建立棧幀等);

二是為其他優化建立良好的基礎。方法內聯膨脹之後可以便於在更大範圍上進行後續的優化手段,可以獲取更好的優化效果。

因此各種編譯器一般都會把內聯優化放在優化序列最靠前的位置。

優化前:

優化後:

這個叫冗餘訪問消除,假設代碼中間註釋掉的“…do stuff…”所代表的操作不會改變b.value的值,那麼就可以把“z=b.value”替換為“z=y”,因為上一句“y=b.value”已經保證了變數y與b.value是一致的,這樣就可以不再去訪問對象b的局部變數了。

優化前:

優化後:

這個叫覆寫傳播,因為這段程式的邏輯之中沒有必要使用一個額外的變數z,它與變數y是完全相等的,因此我們可以使用y來代替z。

優化前:

優化後:

這個叫無用代碼消除,無用代碼可能是永遠不會被執行的代碼,也可能是完全沒有意義的代碼。

經過四次優化之後,前後的代碼所達到的效果是一致的,但是後者比前者省略了許多語句,體現在位元組碼和機器碼指令上的差距會更大,執行效率的差距也會更高。

接下來我們重點講解一下四項有代表性的優化技術:

一、方法內聯

內聯被業內戲稱為優化之母,因為除了消除方法調用的成本之外,它更重要的意義是為其他優化手段建立良好的基礎,我們可以回頭看看前面的案例,如果沒有最開始的方法內聯,後續多數其他優化都無法有效進行。

方法內聯的優化行為理解起來是沒有任何困難的,不過就是把目標方法的代碼原封不動地“複製”到發起調用的方法之中,避免發生真實的方法調用而已。但實際上Java虛擬機中的內聯過程卻遠沒有想象中容易,甚至如果不是即時編譯器做了一些特殊的努力,按照經典編譯原理的優化理論,大多數的Java方法都無法進行內聯。

對於一個虛方法,編譯器靜態地去做內聯的時候很難確定應該使用哪個方法版本,以之前例子所示的b.get()直接內聯為b.value為例,如果不依賴上下文,是無法確定b的實際類型是什麼的。

假如有ParentB和SubB是兩個具有繼承關係的父子類型,並且子類重寫了父類的get()方法,那麼b.get()是執行父類的get()方法還是子類的get()方法,這應該是根據實際類型動態分派的,而實際類型必須在實際運行到這一行代碼時才能確定,編譯器很難在編譯時得出絕對準確的結論。

於是,Java虛擬機引入了類型繼承關係分析技術,這是整個應用程式範圍內的類型分析技術(Class HierarchyAnalysis,CHA),用於確定在目前已載入的類中,某個介面是否有多於一種的實現、某個類是否存在子類、某個子類是否覆蓋了父類的某個虛方法等信息。如果是非虛方法,直接進行內聯就可以了;

如果遇到虛方法,則會向CHA查詢此方法在當前程式狀態下是否真的有多個目標版本可供選擇,如果只有一個版本,直接進行內聯。

不過由於Java程式是動態連接的,說不准什麼時候就會載入到新的類型從而改變CHA結論,因此這種內聯屬於激進預測性優化,必須預留好“逃生門”,即當假設條件不成立時的“退路”。假如在程式的後續執行過程中,虛擬機一直沒有載入到會令這個方法的接收者的繼承關係發生變化的類,那這個內聯優化的代碼就可以一直使用下去。如果載入了導致繼承關係發生變化的新類,那麼就必須拋棄已經編譯的代碼,退回到解釋狀態進行執行,或者重新進行編譯。

若CHA查詢出該方法確實有多個版本的目標方法,那即時編譯器還將進行最後一次努力,使用內聯緩存的方式來縮減方法調用的開銷。這種狀態下方法調用是真正發生了的,但是比起直接查虛方法表還是要快一些。

內聯緩存是一個建立在目標方法正常入口之前的緩存,它的工作原理大致為:在未發生方法調用之前,內聯緩存狀態為空,當第一次調用發生後,緩存記錄下方法接收者的版本信息,並且每次進行方法調用時都比較接收者的版本。如果以後進
來的每次調用的方法接收者版本都是一樣的,那麼這時它就是一種單態內聯緩存。通過該緩存來調用,比用不內聯的非虛方法調用,僅多了一次類型判斷的開銷而已。(這一點和sychronized鎖優化的偏向鎖思路相似)

但如果真的出現方法接收者不一致的情況,就說明程式用到了虛方法的多態特性,這時候會退化成超多態內聯緩存,其開銷相當於真正查找虛方法表來進行方法分派。

二、逃逸分析

逃逸分析的基本原理是:分析對象動態作用域,當一個對象在方法裡面被定義後,它可能被外部方法所引用,例如作為調用參數傳遞到其他方法中,這種稱為方法逃逸;

甚至還有可能被外部線程訪問到,譬如賦值給可以在其他線程中訪問的實例變數,這種稱為線程逃逸;

從不逃逸、方法逃逸到線程逃逸,稱為對象由低到高的不同逃逸程度。

如果能證明一個對象不會逃逸到方法或線程之外(換句話說是別的方法或線程無法通過任何途徑訪問到這個對象),或者逃逸程度比較低(只逃逸出方法而不會逃逸出線程),則可能為這個對象實例採取不同程度的優化,
如:
棧上分配:在Java虛擬機中,Java堆上分配創建對象的記憶體空間幾乎是Java程式員都知道的常識,Java堆中的對象對於各個線程都是共用和可見的,只要持有這個對象的引用,就可以訪問到堆中存儲的對象數據。

虛擬機的垃圾收集子系統會回收堆中不再使用的對象,但回收動作無論是標記篩選出可回收對象,還是回收和整理記憶體,都需要耗費大量資源。如果確定一個對象不會逃逸出線程之外,那讓這個對象在棧上分配記憶體將會是一個很不錯的主意,對象所占用的記憶體空間就可以隨棧幀出棧而銷毀。

在一般應用中,完全不會逃逸的局部對象和不會逃逸出線程的對象所占的比例是很大的,如果能使用棧上分配,那大量的對象就會隨著方法的結束而自動銷毀了,垃圾收集子系統的壓力將會下降很多。
棧上分配可以支持方法逃逸,但不能支持線程逃逸。

標量替換:若一個數據已經無法再分解成更小的數據來表示了,Java虛擬機中的原始數據類型(int、long等數值類型及reference類型等)都不能再進一步分解了,那麼這些數據就可以被稱為標量。

相對的,如果一個數據可以繼續分解,那它就被稱為聚合量,Java中的對象就是典型的聚合量。如果把一個Java對象拆散,根據程式訪問的情況,將其用到的成員變數恢復為原始類型來訪問,這個過程就稱為標量替換。

假如逃逸分析能夠證明一個對象不會被方法外部訪問,並且這個對象可以被拆散,那麼程式真正執行的時候將可能不去創建這個對象,而改為直接創建它的若幹個被這個方法使用的成員變數來代替。

同步消除:線程同步本身是一個相對耗時的過程,如果逃逸分析能夠確定一個變數不會逃逸出線程,無法被其他線程訪問,那麼這個變數的讀寫肯定就不會有競爭,對這個變數實施的同步措施也就可以安全地消除掉。

三、公共子表達式消除

如果一個表達式E之前已經被計算過了,並且從先前的計算到現在E中所有變數的值都沒有發生變化,那麼E的這次出現就稱為公共子表達式。對於這種表達式,沒有必要花時間再對它重新進行計算,只需要直接用前面計算過的表達式結果代替E。

如果這種優化僅限於程式基本塊內,便可稱為局部公共子表達式消除,如果這種優化的範圍涵蓋了多個基本塊,那就稱為全局公共子表達式消除。

四、數組邊界檢查消除

Java語言是一門動態安全的語言,對數組的讀寫訪問也不像C、C++那樣實質上就是裸指針操作。

如果有一個數組foo[],在Java語言中訪問數組元素foo[i]的時候系統將會自動進行上下界的範圍檢查,即i必須滿足“i>=0 && i<foo.length”的訪問條件,否則將拋出一個運行時異常:java.lang.ArrayIndexOutOfBoundsException。

這對軟體開發者來說是一件很友好的事情,即使程式員沒有專門編寫防禦代碼,也能夠避免大多數的溢出攻擊。但是對於虛擬機的執行子系統來說,每次數組元素的讀寫都帶有一次隱含的條件判定操作,對於擁有大量數組訪問的程式代碼,這必定是一種性能負擔。

無論如何,為了安全,數組邊界檢查肯定是要做的,但數組邊界檢查是不是必須在運行期間一次不漏地進行則是可以“商量”的事情。例如下麵這個簡單的情況:數組下標是一個常量,如foo[3],只要在編譯期根據數據流分析來確定foo.length的值,並判斷下標“3”沒有越界,執行的時候就無須判斷了。

更加常見的情況是,數組訪問發生在迴圈之中,並且使用迴圈變數來進行數組的訪問。如果編譯器只要通過數據流分析就可以判定迴圈變數的取值範圍永遠在區間[0,foo.length)之內,那麼在迴圈中就可以把整個數組的上下界檢查消除掉,這可以節省很多次的條件判斷操作。

參考資料:

深入理解虛擬機-第3版-周志明著

為什麼寫文章 ?(若有錯誤,希望得到你的指正,若有問題,都可評論,我將會積極回覆)

在作者剛入行時,會遇到很多無法理解的問題,便經常向前輩請教問題,或是於網路之中苦苦尋找答案,經常被一些晦澀難懂的表達折磨的死去活來,現作者是一名擁有多年經驗的IT從業者,希望能夠將自己的知識以一種形象的方式輸出,先從虛擬機開始分享,之後會寫更多的專欄,最新的分享將會先在公眾號發佈,謝謝讀者的關註


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 【狂神說Java】Java零基礎學習筆記-JavaSE總結 ## JavaSE總結: ![image](https://img2023.cnblogs.com/blog/3231511/202308/3231511-20230817171925456-1307925972.jpg) ## 🎉� ...
  • # 【狂神說Java】Java零基礎學習筆記-異常 ## 異常01:Error和Exception ### 什麼是異常 - 實際工作中,遇到的情況不可能是非常完美的。比如:你寫的某個模塊,用戶輸入不一定符合你的要求、你的程式要打開某個文件,這個文件可能不存在或者文件格式不對,你要讀取資料庫的數據,數 ...
  • ### 1. json.load(json_data)與json.dump(python_data) json.load()用來將讀取json文件,json.dump()用來將數據寫入json文件 ### 2. json.loads()與json.dumps() - json.dumps 將 Pyt ...
  • 使用python爬蟲爬取數據的時候,經常會遇到一些網站的反爬蟲措施,一般就是針對於headers中的User-Agent,如果沒有對headers進行設置,User-Agent會聲明自己是python腳本,而如果網站有反爬蟲的想法的話,必然會拒絕這樣的連接。 而修改headers可以將自己的爬蟲腳本 ...
  • C++ STL(Standard Template Library)是C++標準庫中的一個重要組成部分,提供了豐富的模板函數和容器,用於處理各種數據結構和演算法。在STL中,排序、算數和集合演算法是常用的功能,可以幫助我們對數據進行排序、統計、查找以及集合操作等。STL提供的這些演算法,能夠滿足各種數據處... ...
  • 為衡量個人能力水平自創的一套評分機制,根據時間、代碼行數、基礎理論三個變數生成。使用lua語言編寫,輸出成三個markdown表格。 ...
  • > 本文以一個通過正常註冊攔截器流程註冊攔截器失敗的實際場景,來帶領大家閱讀源碼,體會Spring的HandlerInterceptor攔截器整個工作流程 ### 簡單認識 org.springframework.web.servlet.HandlerInterceptor是Spring框架中的一個 ...
  • # 【狂神說Java】Java零基礎學習筆記-面向對象 ## 面向對象01:什麼是面向對象 ### 面向過程&面向對象 - 面向過程思想 - 步驟清晰簡單,第一步做什麼,第二步做什麼.... - 面對過程適合處理一些較為簡單的問題 - 面向對象思想 - 物以類聚,**分類**的思維模式,思考問題首先 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...