大資料中的Java虛擬機器最佳化

大雄45發表於2023-02-18
導讀 Java基於JVM執行的特性使得Java程式可以一次編寫,多處執行,擺脫分散式叢集中不同作業系統和硬體處理框架帶來的束縛,使得開發者可以更加專注於程式碼邏輯的開發。

近年以來,大資料應用取得了長足的進展,各種大資料處理框架也應運而生,並得到了業界的高度認可,如Hadoop生態、Spark系列、Flink、Cassandra、Hive等等。這類程式設計模型通常採用分治的思想,將大的資料處理作業拆分為多個小的計算任務,分配到分散式叢集中的不同節點中執行,然後將結果匯聚起來,得出最終結果。

由於使用習慣等關係,業內主流的大資料處理框架都採用Java語言進行編寫。原因在於以下幾點:

1、很多程式開發人員對於Java語言比較熟悉,使用起來輕車熟路;

2、Java提供了便捷的自動記憶體管理機制,避免了使用者在處理記憶體過程中可能出現的問題;

3、Java基於JVM執行的特性使得Java程式可以一次編寫,多處執行,擺脫分散式叢集中不同作業系統和硬體處理框架帶來的束縛,使得開發者可以更加專注於程式碼邏輯的開發;

4、Java語言擁有成熟的社群和豐富的程式設計資源,可以實現快速開發,出現問題也可以快速尋求幫助。

大資料中的Java虛擬機器最佳化大資料中的Java虛擬機器最佳化

正是基於這些優點,Java語言成為了目前最主流大資料程式設計技術。但是,在實際使用的過程中,開發者們發現了一系列JVM相關的效能瓶頸,主要包括以下幾個方面:

1、垃圾回收(GC)佔用時間長,在一些大資料應用中,GC時間甚至可以達到總執行時間的50%;

2、GC頻率高,造成任務執行頻繁暫停,應用吞吐率降低,響應延遲升高;

3、GC演算法擠佔應用執行緒CPU資源,存在GC執行緒競爭時,大資料應用執行時間增長可達60%;

4、資料物件在分散式節點間傳輸時需要序列化和反序列化,在某些大資料應用中,用時佔比可達30%;

5、JVM冷啟動時需要大量的類載入和程式碼即時編譯工作,在應用執行中的用時可達數十秒;

6、JVM執行和維護需要記憶體消耗,在記憶體緊張的情況下,可能因為記憶體耗盡或記憶體碎片觸發OOM錯誤。

大資料中的Java虛擬機器最佳化大資料中的Java虛擬機器最佳化

總的來說,這些問題的產生,可以歸納為以下一些原因:

1、記憶體使用壓力增大

與普通的Java應用不同,大資料應用是“記憶體密集”型的,應用的記憶體使用量更大,在大資料處理框架下,JVM的記憶體使用壓力具體來源於:

(1)大資料應用資料計算和儲存產生的大量記憶體消耗,大量資料在計算過程中需要同時被讀取到記憶體中,而一些應用為了更進一步加快處理速度,將中間資料的聚合和可重用資料也快取在記憶體當中,這決定了JVM在執行大資料應用時將面對更大的記憶體使用量;

(2)資料在JVM堆記憶體當中以物件的形式儲存需要額外的記憶體佔用,物件在JVM當中的資料結構包含了物件頭以及對其它物件的引用,而資料本身在物件中的空間佔比往往不超過一半。這些物件的外殼伴隨著資料快取在記憶體當中,也需要佔用相當數量的空間;

由於JVM垃圾回收機制的原因,會經常觸發全域性暫停,而這個問題很難透過簡單的增減記憶體大小來解決,如果降低記憶體大小,GC的觸發頻率會增加,物件被掃描和的去的次數增加,應用程式的吞吐量相應降低。可用記憶體不足還會影響到應用的正常快取和處理機制,甚至引發記憶體溢位。而如果提升記憶體大小,單次GC則需要處理更多的資料物件,平均的暫停時間加長,應用程式的最大延遲相應增加。對於週期性標記掃描的GC演算法而言,還會在最終觸發GC之前消耗更多CPU時序進行不必要的標記。

2、記憶體使用模式變化

大資料應用中資料在記憶體當中保留的時間週期與普通應用不盡相同。在傳統應用中,堆記憶體中建立的絕大部分物件在產生之後不久就不再被使用,經典的GC演算法正是基於這種記憶體使用模式,將堆記憶體進行粗粒度的年代劃分,絕大部分瞬時物件會在針對年輕代的Minor GC當中很快被清理。而大資料應用產生的物件有兩種,一種是由控制大資料處理框架執行邏輯的程式碼產生的,即控制路徑物件,它們的記憶體使用模式一般依舊符合弱世代假設。另一種是輸入資料和計算中間資料在大資料處理框架中封裝生產的,統稱為資料路徑物件。這種物件的記憶體使用模式要更加複雜,它們可能在記憶體中長時間累積或快取,也可能在一個迭代輪次後被清理和輸出。通常來說,資料路徑所建立的物件數量遠超控制路徑。傳統GC演算法並不能適應大資料環境下記憶體使用模式的這種變化,原因在於:

(1)當前GC演算法下,長時間存活的資料路徑物件最終都會晉升到老年代中,它們在數次Minor GC當中倖存並最終晉升的過程中,需要在記憶體中多次移動。而物件移動是GC迴圈當中最耗時的部分,每一次移動都意味著記憶體讀寫,而記憶體位置的改變也需要對相關引用的指標進行更新。考慮到資料路徑物件的數量極為龐大,整個晉升過程會消耗大量CPU時間,觸發多次GC暫停;

(2)資料路徑物件在晉升到老年代之後,在作業執行的時間尺度上,短時間內也不會被回收。傳統的GC演算法不會考慮這些物件的存活時間,在涉及到老年代空間的MajorGC或者Mixed GC之前還是會整個堆記憶體空間進行標記掃描,這些標記掃描過程對於長時間存活的資料物件來說是不必要的。當長時間存活物件佔用老年代的比例過高,每次傳出較大代價的Major GC就只能回收有限大小的空間,可能造成GC頻繁觸發,部分快取資料被迫轉移到磁碟,甚至出現OOM錯誤,浪費大量的CPU時間和全域性暫停時間,影響到應用執行效率。

大資料中的Java虛擬機器最佳化大資料中的Java虛擬機器最佳化

3、JVM與上層框架存在隔閡

大資料處理框架將計算任務分配到各個執行器JVM節點之後,並不會干預JVM的具體執行過程,每個執行器JVM獨立執行,並不感知分散式叢集中其它執行器JVM的執行情況,作業的整體進度,以及叢集和節點的記憶體資源使用情況,只是根據自身的執行狀態作出觸發GC,調整堆記憶體,進行程式碼即時編譯等決策,而這些決策從歷史和全域性的角度上觀察可能並不是最優的。原因在於:

(1)JVM不清楚任務執行產生的資料物件特徵,例如物件數量、記憶體佔用大小、生命週期等,只能根據弱世代假說,對所有物件進行統一的管理。由於大資料應用產生的大量物件長時間存活,JVM的記憶體管理效率會受到嚴重影響,而這些物件本可以透過大資料框架對使用者程式碼和資料流的全域性靜態分析進行甄別。

(2)大資料處理框架並不考慮JVM具體的記憶體管理機制,將所有JVM節點的記憶體當做連續的全域性地址空間,但是實際上JVM在GC演算法下對堆記憶體採取分代管理,存在非連續區域,物件在記憶體中離散分佈,另外大資料處理框架在採用全域性地址空間的物理架構下,可能產生大量跨節點對物件引用,給JVM的GC任務帶來了遠端記憶體訪問的負擔。

(3)大資料處理框架下的JVM之間不清楚彼此的執行情況,如果大資料操作需要在各個JVM之間同步,由於JVM獨立進行GC決策,大資料操作的執行就可能被不同的JVM的GC連續打斷,另外由於互相不感知,處於同一物理節點的JVM之間可能記憶體資源分配不合理,而大資料框架在相關問題上缺少統籌協調。

因此,開發者們需要針對這些問題產生的原因,進行針對性最佳化。我們下次文章將會繼續討論這個問題。

原文來自:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2935418/,如需轉載,請註明出處,否則將追究法律責任。

相關文章