從整體結構到分散式系統世界,應用程式開發已經走過了漫長的道路。雲端運算和微服務架構的大規模採用極大地改變了伺服器應用程式的創建和部署方式。我們現在擁有獨立、單獨部署的可立即投入使用的服務,而不是龐大的應用程式伺服器
在需要時。
然而,區塊上可能影響這種平穩運行的新玩家可能是「冷啟動」。 當第一個請求在新產生的工作執行緒上處理時,冷啟動就會啟動。這種情況需要在處理實際請求之前進行語言執行時間初始化和服務配置初始化。與冷啟動相關的不可預測性和執行速度較慢可能會違反雲端服務的服務等級協定。那麼,如何應對這種日益增長的擔憂呢?
為了解決冷啟動的低效率問題,我們開發了一種新穎的方法,涉及點分析、構建時的應用程式初始化、堆快照和提前 (AOT) 編譯。此方法在封閉世界假設下運行,要求所有 Java 類別在建置時都已預先確定並可存取。在此階段,全面的點分析確定所有可存取的程式元素(類別、方法、欄位),以確保僅編譯必要的 Java 方法。
應用程式的初始化程式碼可以在建置過程中執行,而不是在執行時執行。這允許預先分配 Java 物件並建立複雜的資料結構,然後在運行時透過「映像堆」提供這些資料結構。此映像堆整合在可執行檔中,在應用程式啟動時提供立即可用性。
持續迭代執行點分析和快照,直到達到穩定狀態(定點),從而優化啟動時間和資源消耗。
我們系統的輸入是 Java 字節碼,它可能源自 Java、Scala 或 Kotlin 等語言。這個過程統一處理應用程式、其庫、JDK 和 VM 元件,以產生特定於作業系統和體系結構的本機可執行檔 - 稱為「本機映像」。建置過程包括迭代點分析和堆疊快照,直到達到固定點,從而允許應用程式透過註冊的回調主動參與。這些步驟統稱為本機映像建置流程(圖 1)
圖 1 – 本機映像建置流程(資料來源:redhat.com)
我們採用點分析來確定執行時間類別、方法和欄位的可及性。點到分析從所有入口點(例如應用程式的主要方法)開始,迭代遍歷所有可傳遞可達的方法,直到到達固定點(圖 2)。
圖 2 – 分析點
我們的指向分析利用編譯器的前端將 Java 字節碼解析為編譯器的高階中間表示(IR)。隨後,IR 被轉換為類型流程圖。在此圖中,節點表示對物件類型進行操作的指令,而邊表示節點之間的定向使用邊,從定義指向使用。每個節點維護一個類型狀態,由可以到達該節點的類型清單和空值資訊組成。類型狀態透過使用邊傳播;如果節點的類型狀態發生變化,則此變更將傳播到所有用途。重要的是,類型狀態只能擴展;新類型可以添加到類型狀態,但現有類型永遠不會被刪除。此機制確保
分析最終收斂到一個固定點,導致終止。
指向分析指導初始化程式碼在到達本地固定點時的執行。此程式碼起源於兩個不同的來源:類別初始值設定項和在建置時透過功能介面批次執行的自訂程式碼:
類別初始值設定項: 每個 Java 類別都可以有一個由
明確回調:開發人員可以透過我們系統提供的鉤子實現自訂程式碼,在分析階段之前、期間或之後執行。
這裡提供了用於與我們的系統整合的 API。
boolean isReachable(Class<?> clazz); boolean isReachable(Field field); boolean isReachable(Executable method);
更多信息,請參閱 QueryReachabilityAccess
void registerReachabilityHandler(Consumer<DuringAnalysisAccess> callback, Object... elements); void registerSubtypeReachabilityHandler(BiConsumer<DuringAnalysisAccess, Class<?>> callback, Class<?> baseClass); void registerMethodOverrideReachabilityHandler(BiConsumer<DuringAnalysisAccess, Executable> callback, Executable baseMethod);
更多信息,請參閱BeforeAnalysisAccess
在此階段,應用程式可以執行自訂程式碼,例如物件分配和較大資料結構的初始化。重要的是,初始化程式碼可以存取當前的分析狀態點,從而啟用有關類型、方法或欄位的可及性的查詢。這是使用DuringAnalysisAccess 提供的各種isReachable() 方法來完成的。利用這些信息,應用程式可以建立針對應用程式的可到達段進行最佳化的資料結構。
最後,堆疊快照透過像靜態欄位一樣跟隨根指標建立物件圖,以建立所有可存取物件的全面視圖。然後該圖填入本機影像的
圖像堆,確保應用程式的初始狀態在啟動時有效載入。
為了產生可達物件的傳遞閉包,該演算法遍歷物件字段,使用反射讀取它們的值。需要注意的是,映像產生器在 Java 環境中運作。在此遍歷期間,僅考慮由指向分析標記為「已讀」的實例欄位。例如,如果一個類別有兩個實例字段,但其中一個未標記為已讀,則透過未標記字段可存取的物件將從圖像堆中排除。
當遇到先前未透過指向分析識別其類別的欄位值時,該類別將被註冊為欄位類型。此註冊可確保在點分析的後續迭代中,新類型會傳播到類型流程圖中的所有欄位讀取和傳遞用法。
下面的程式碼片段概述了堆快照的核心演算法:
Declare List worklist := [] Declare Set reachableObjects := [] Function BuildHeapSnapshot(PointsToState pointsToState) For Each field in pointsToState.getReachableStaticObjectFields() Call AddObjectToWorkList(field.readValue()) End For For Each method in pointsToState.getReachableMethods() For Each constant in method.embeddedConstants() Call AddObjectToWorkList(constant) End For End For While worklist.isNotEmpty Object current := Pop from worklist If current Object is an Array For Each value in current Call AddObjectToWorkList(value) Add current.getClass() to pointsToState.getObjectArrayTypes() End For Else For Each field in pointsToState.getReachableInstanceObjectFields(current.getClass()) Object value := field.read(current) Call AddObjectToWorkList(value) Add value.getClass() to pointsToState.getFieldValueTypes(field) End For End If End While Return reachableObjects End Function
綜上所述,堆快照演算法透過系統地遍歷可達物件及其欄位來有效地建立堆疊快照。這可確保影像堆中僅包含相關對象,從而優化本機影像的效能和記憶體佔用。
總而言之,堆快照過程在原生鏡像的創建中起著至關重要的作用。透過系統地遍歷可達物件及其字段,堆快照演算法建立了一個物件圖,該物件圖表示可達物件從根指標(例如靜態字段)的傳遞閉包。然後將該物件圖作為映像堆嵌入到本機映像中,作為本機映像啟動時的初始堆疊。
在整個過程中,演算法依賴分析點的狀態來確定哪些物件和欄位與包含在影像堆中相關。考慮由點分析標記為「已讀」的物件和字段,而排除未標記的實體。此外,當遇到以前未見過的類型時,演算法會將它們註冊以便在點分析的後續迭代中傳播。
整體而言,堆疊快照透過確保映像堆中僅包含必要的物件來最佳化本機映像的效能和記憶體使用情況。這種系統方法提高了本機影像執行的效率和可靠性。
以上是透過靜態分析、映像初始化和堆疊快照提高效能的詳細內容。更多資訊請關注PHP中文網其他相關文章!