為什麼Java有GC調優而沒聽說過有CLR的GC調優?

来源:https://www.cnblogs.com/InCerry/archive/2022/07/26/clr-jvm-gc-opt-1.html
-Advertisement-
Play Games

## 前言 在很多的場合我都遇到過一些群友提這樣的一些問題: 為什麼Java有GC調優而CLR沒有聽說過有GC調優呢? 到底是Java的JVM GC比較強還是C#使用的.NET CLR的GC比較強呢? 其實業內已經有幾位大佬的高贊文章和大家分享一下,主要討論JVM和CLR還有GC調優,今天就借用大 ...


前言

在很多的場合我都遇到過一些群友提這樣的一些問題:

  • 為什麼Java有GC調優而CLR沒有聽說過有GC調優呢?
  • 到底是Java的JVM GC比較強還是C#使用的.NET CLR的GC比較強呢?
    其實業內已經有幾位大佬的高贊文章和大家分享一下,主要討論JVM和CLR還有GC調優,今天就借用大佬的回答給大家分享一下。

R大的回答

首先給大家介紹一下R大,R大網名叫RednaxelaFX,南京大學畢業巨佬,主攻高級編程語言虛擬機的設計與實現,對於C# .NET CLR有非常深入的研究和瞭解。先後加入阿裡、Oracle、Azul System從事JVM虛擬機開發的工作(HotSpot JVM and Zing JVM)。現在在Databricks從事Spark開發工作。

R大社交主頁

iteye博客:https://www.iteye.com/blog/user/rednaxelafx
知乎主頁: https://www.zhihu.com/people/rednaxelafx
GitHub: https://github.com/rednaxelafx

R大回答相關問題摘抄

為什麼聽說過 JVM 調優,卻沒聽說過 CLR 調優?

RednaxelaFX的回答 - 知乎 https://www.zhihu.com/question/52900051/answer/132583244

這裡R大主要就CLR上幾種調優方式和為什麼CLR不需要xmx、xmn等參數給出了原因

因為題主您就是沒聽說過 >_<

假定題主說的是下麵三個層面的調優的頭兩種情況:

  1. 對VM的配置參數調優(例如GC參數)
  2. 對應用程式自身的代碼調優
  3. 對VM自身的實現調優

(2)的話無論什麼語言什麼環境都好,追求性能的人肯定都有在做。Java和.NET都有好用的profiler可以幫助這方面的調查和調優。

有時候參數調優還不夠的地方,也只能自己改自己的應用代碼來解決問題了。請看一個經典案例:

In managed code we trust, our recent battles with the .NET Garbage Collector

(3)的話,CLR還沒開源的時候,也無從調起。倒是有不少人給Mono貢獻改進性能的patch,也算是廣義上“.NET”的VM實現層面調優吧。

在CoreCLR開源後,也有不少人給CoreCLR貢獻各種patch呢,包括“調優”。

.NET碼農們以前時不時會中招的一種地方是程式進入GC的耗時(time-to-GC),也就是從CLR說“我要開始做GC了”到“真正開始做GC”之間的耗時。這裡主要開銷來自請求所有應用線程暫停(SuspendEE),這些線程要多久才完成對該請求的響應。聽說過這個過程中會發生“250ms的倍數的等待時間”不?請跳傳送門:

Garbage Collection Thread Suspension Delay (250ms Multiples)

像這種問題就是不進到VM內部做修改的話無法解決的。

(1)的話,其實就算當年CLR還沒開源的時候,CLR也是有調優參數可以配置的呢。

最經典的就是選擇試用Workstation GC(WKS GC)或者Server GC(SVR GC)。見過<gcServer>參數不?

後來可以配置使用Concurrent GC、Background Workstation GC、Background Server GC等。

用戶還可以在代碼里通過 GCSettings.LatencyMode 屬性來影響GC的行為。

看,調優參數列表之一:
Runtime Settings Schema
不過CLR跟HotSpot VM在配置上有一個顯著的區別,就是CLR不需要用戶指定一個“GC堆的最大大小”。這跟CLR的GC堆的基礎設計思路有關係。

HotSpot VM的GC堆一定要使用連續的虛擬地址空間。VM在啟動的時候會一口氣reserve GC所需要的整個地址空間,然後再按需commit。-Xmx會參與到GC堆最大大小的計算中。

CLR的GC堆則是分段式的(segemented),GC堆所用的空間會一個個segment分配,用滿了一個再去分配一個新的;segment不需要在連續的地址空間上。這樣GC堆可以按需自動增長或者縮減,可以一直增長到耗盡虛擬地址空間或者達到配額。

CLR這種分段式GC堆的好處是,在Windows上,特別是32位Windows上,虛擬地址空間中用戶程式可以用的部分是比較零碎的,想要用到盡就不能對“連續的地址空間”有太多要求,這種條件下CLR跑在Windows上就可以充分利用資源。

而且這樣一來,用戶就不用頭疼實現想好要配置多大的堆給CLR用了。反正它需要用多少會自己去增長。這用戶體驗就比絞盡腦汁想個好-Xmx要爽。

這種做法的壞處…怎能沒有壞處呢。壞處也有若幹。其中一個就是在這樣的堆上實現的分代式GC的write barrier效率會比HotSpot那種用連續地址空間的要差一些。而且segmented heap實現起來也稍微複雜一些。

CLR 相比 JVM有哪些先進之處?

CLR 相比 JVM有哪些先進之處? - RednaxelaFX的回答 - 知乎 https://www.zhihu.com/question/39400401/answer/81293750

這裡R大主要介紹了下CLR和JVM的不同,和下麵hez大佬的回答可以對應起來

雖然只寫了一半但還是先發出來免得坑掉。後面邊討論邊更新吧…

這個問題原文是:

CLR 相比 JVM有哪些先進之處?

留下備份。

首先這個問題按其原樣無法回答,因為CLR與JVM不是可比較的對象。

CLR(Common Language Runtime)是CLI(Common Language Infrastructure)規範中的VES(Virtual Execution System)的一種具體實現,而“JVM”不指定實現的話只能說是一種抽象的規範。

適合比較的對象是:

  • 規範層面:CLI vs JVM
  • 實現層面:CLR / Mono 等 vs HotSpot VM / J9 VM / JRockit VM 等

從規範層面看的話,CLI規範

ECMA-335

當前最新的版本是2012年出的第6版。我沒太跟進新的CLI規範所以不確定這個版本的CLI與哪個版本的.NET Framework里的CLR對應。

JVM規範

The Java® Virtual Machine Specification

當前最新的版本則是2015年出的Java SE 8版。

在規範層面上,當前的CLI完勝當前的JVM。

《Virtual Machines: Versatile Platforms for Systems and Processes》

一書中有一章專門對比介紹了CLI與JVM的設計,值得一讀。

JVM規範由Sun最初的JVM實現(後來稱為Classic VM)抽象而來,然後幾乎沒有大的改動。後來的更新主要添加了Class文件對泛型信息的有限記錄、註解(annotation)支持、位元組碼校驗器的更新(split verifier / type checking verifier)、invokedynamic及MethodHandle支持、default method支持等。

最初的JVM規範雖然也提到它“可以支持多種語言”,但主要目的還是支持Java一種語言的執行,直到Java 7添加了JSR 292(invokedynamic與MethodHandle)後才有了專門為Java以外的語言設計的功能。

CLI由.NET最初的CLR實現抽象而來,然後與CLR一起逐漸進化。它出現的時間畢竟比JVM規範晚許多,而且一開始就以支持多種語言、多種範型的執行與互操作為設計目的,自然設計得更完善。對歷史有興趣的同學可以搜搜“Project 7”看。

Interviewer: I've heard that there was a project where Microsoft started to inte...

Don Syme: That's a small part of the sequence. The visional design of the .NET platform was very much expected to be a multi-language platform from the start. Right back in 1998, just in fact as our research group in programming languages started at Microsoft and I joined the team and then other 10 of us joined the team, we were approached by a guy called James Plamondon, who started the project called Project 7, which was about getting 7 academic and 7 industrial programming languages on each side to target the .NET common language runtime and really check out if it was good enough, to see if design changes could be made early on in the design process of .NET to make sure it was good enough for a range of programming languages.

Project 7的參與方嘗試了將許多種語言移植到CLR上,包括C、Pascal、Cobol (Fujitsu)、Fortran (Salford)、Haskell、Standard ML、Eiffel、Active Oberon for .NET (ETH)、Gardens Point Component Pascal (QUT)等等。

後來還有更進一步的“Project 7+”。

Technical Overview of the Common Language Runtime

要追尋CLR更早的黑歷史就不得不提微軟的Visual J++。請跳傳送門:

微軟當年的 J++ 究竟是什麼?為什麼 Sun 要告它? - RednaxelaFX 的回答

下麵先列舉一些點提醒我回頭更新…

Assembly vs Class文件

  • 常量池以stream的方式存在
  • 可以有strong name

CIL(Common Intermediate Language) / MSIL vs Java位元組碼

  • CIL不僅有二進位形式的規範,而且還有標準的文本形式語法;Java位元組碼只有二進位形式的規範
  • 局部變數區里無論什麼類型的值都使用1個slot,而JVM則是long和double用相鄰的2個slot
  • 異常處理支持filter
  • 支持具現化泛型(reified generics)
  • 泛型支持聲明點協變/逆變(declaration-site variance)
    • Java語言層面的泛型則是支持使用點協變/逆變(use-site variance)
  • 支持無符號整數類型(unsigned)
  • 支持用戶自定義值類型(value type)
  • 支持對用戶自定義值類型指定顯式記憶體佈局,可以模擬C的struct和union以便互操作(StructLayout)
  • 支持檢查溢出的算術運算(checked arithmetic)
  • 支持有保證的尾調用(tail call)
  • 支持unsafe verifiable代碼
  • 支持輕量代碼生成(LCG,Lightweight Code Generation)
  • 支持委托類型(Delegate / MulticastDelegate)
  • CLI Delegate的single cast版本與Java 7的MethodHandle有相似之處
  • 支持方便的P/Invoke和Reverse P/Invoke
  • 不支持自定義類載入器(custom class loader)。JVM上過於靈活的ClassLoader簡直是頭疼的要命,不支持這個簡直好。

CLI里藏的私貨:Assembly採用PE(Portable Executable)格式。PE是Windows上原生的可執行文件格式。

CLR與Windows的整合

  • Windows Loader可以直接識別.NET Assembly並啟動CLR來執行程式。
  • Windows對GAC(Global Assembly Cache)有文件系統層面的特殊處理,“Fusion”。
  • 與COM有良好的互操作能力。畢竟CLR原本就是希望設計成下一代COM,原本還用過COR這個名字(COM Object Runtime)。
  • 分塊式GC堆(Chunked GC Heap),可以自動擴張和收縮GC堆的大小,並且在Windows這種地址空間比較破碎的環境中可以有效的利用地址空間里的空隙。對應的,JRockit在Windows上則是用filler object來利用空隙。
  • GC堆分為多個空間,特別是高頻堆/低頻堆(high-frequency heap / low-frequency heap)的劃分很有趣。
  • GC支持固定住對象(Object Pinning)
  • GC提供API讓應用程式可以指定native對象的記憶體壓力,以便GC與native協調工作(System.GC.AddMemoryPressure Method (Int64)
  • 支持Windows的結構化異常處理(SEH)。

CLR 與 一些JVM實現之間有對應物的

  • CLR:DAC / SOS擴展;HotSpot VM:Serviceability Agent;J9 VM:DTFJ / jdmpview
  • CLR:CAS(Code Access Security)與 JVM的沙箱

hez大佬的回答

再來給大家介紹一下hez2010大佬,今年剛從中山大學畢業,對於C++、.NET、C#、Rust等平臺語言都有深入的研究,經常在博客園看文章的小伙伴應該早就看過hez佬的博文。他是.NET Runtime開源項目的持續貢獻者、Microsoft Student Partner、可以說年少有為。

hez大佬的社交主頁

個人博客:https://hez2010.com/
博客園:https://www.cnblogs.com/hez2010/
知乎:https://www.zhihu.com/people/hez2010
GitHub:https://github.com/hez2010

hez大佬回答相關問題摘抄

.NET Core已經開源好幾年了, 為什麼不像JVM那樣很多人研究和調優其GC演算法?

hez2010的回答 - 知乎 https://www.zhihu.com/question/365482363/answer/2389471084

這和虛擬機(這裡指 JVM 和 CoreCLR)本身的特性有關係,由於基礎設施的不同造成開發人員解決問題的取向不同。

當開發者遇到 GC 導致的問題時:

Java 開發者選擇調優 GC,例如設置堆大小、調整各代和 region 的預算等等,最終使得“滿足 GC 觸發的條件”變得不那麼容易滿足來緩解這個問題,代價自然是記憶體占用更高,畢竟只要記憶體是無限的那就不需要 GC,同理只要 GC 對堆大小容忍度夠高不去回收或者少回收,自然就能減輕 GC 帶來的問題。因此開發者需要根據自身的 workload 來調配這些參數,最終達到一個既不會占用大的離譜甚至導致 OOM 錯誤,又不會導致頻繁 GC 的平衡點。

而 .NET 開發者則會選擇優化自己的代碼,既然熱路徑中對象在托管堆記憶體上大量分配造成了頻繁的 GC 導致出了性能問題,那乾脆不用 class,轉而去用 struct、stackalloc、fixed buffer、NativeMemory、Span 甚至指針,小對象什麼的全都在棧上分配,用完自動隨著棧的銷毀而銷毀,甚至 JIT 可以直接把小值類型對象優化到寄存器裡面,連記憶體分配都不需要;堆記憶體也不需要 GC 托管堆了,直接用 NativeMemory 自己分配自己回收管理,全程完全不需要 GC 介入。這麼做的指導思想很簡單,既然是 GC 導致的問題,那麼直接從源頭解決:不分配托管堆記憶體就不需要 GC。

由於 CoreCLR 從一開始就是支持值類型、指針和非托管堆的,因此有能力的開發者可以在需要低延時的場景中(例如游戲)選擇局部自行管理記憶體從而無需 GC;而其他大多數情況下 GC 並不會導致問題,因此這些時候開發者也可以充分利用 GC 帶來的便利提升開發效率。

而 JVM 並沒有這樣的設施,因此開發者需要將解決問題的方法聚焦到如何讓 GC 適應自己的 workload 上,通過調優一系列的參數來緩解因為 GC 導致的問題,再有就是通過 JVM 團隊改進 GC 的演算法來儘可能讓 GC 暫停線程的時間變短。

另外,這也使得 JVM 和 CoreCLR 的工作重心完全不同,在 CoreCLR 上一個軟實時 GC(ZGC)的重要性遠遠沒有 JVM 上來的高,因此優先順序就會變低。在 JVM 上 ZGC 是理所當然的重要,而在 CoreCLR 上卻並不是,相反,.NET 工作重心則聚焦在改進 struct 相關的底層代碼編寫體驗上:例如允許棧對象(ref struct)持有其他棧對象的引用(ref field),允許 ref struct 作泛型參數和約束,允許在棧上分配任何對象,以及棧對象生命周期的管理等等。

不過最近 CoreCLR 的 GC 團隊也逐漸開始有興趣實現軟實時 GC 以改進游戲等低延時場景的開發體驗(註意我說的是開發體驗,因為讓 GC 全盤管理堆記憶體可以很大程度上提升開發效率並避免記憶體安全問題,只是單純要求低延時甚至 0 延時的話現在完全可以通過繞開 GC 做到),這也是建立在其他高優先順序工作(例如 region 改造和 DPAD 等)基本都完成的基礎上才輪到的。

至於某些極端情況,例如要求可預測的性能、絕對 0 GC 延時的,唯一辦法只有繞開 GC,這種情況即使 ZGC 也無法對應。

更新:

補充一下,.NET 的 GC 是可以直接替換實現的,通過設置環境變數 DOTNET_GCName 指向你按照介面自己實現的 GC 的路徑,就能做到替換掉自帶的 GC:

https://docs.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#standalone-gc​docs.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#standalone-gc

所以要是真的對自帶 GC 不滿的話,可以考慮自己寫 GC 來用。

總結

兩位大佬回答的比較好,基本原因給說的比較明白了,在我看來兩者的比較如下:

  • 從大多數人所認為“調優”選項來說,其實.NET CLR GC也是有調優選項的,只是因為CLR的GC預設使用了Segment方式來組織記憶體,會動態的申請和歸還記憶體空間,所以不需要像JVM一些GC一樣去人為的設置-xmx-xmn-xms這些選項。
  • 現在的.NET的GC和JVM上的GC(指G1)其實是平分秋色,因為都是為高吞吐量設計的分代並行GC,在高吞吐量下都有著不錯的表現,當然各自也有優化空間。
  • 受制於JVM平臺沒有自定義值類型、stackalloc、NativeMemory、Span和指針等特性,.NET這邊能無分配或者棧上分配地方JVM上需要堆分配。所以JVM平臺上的GC壓力會比CLR上的壓力更大一些(雖然有逃逸分析、標量替換等等JIT輔助技術,但是沒有原生那麼快)。
  • JVM平臺開源的比較早,所以它的GC有非常多的選擇,雖然造成初學者面臨很多調優和選擇的問題,但是GC的豐富性這一點是.NET平臺上所缺失的。
  • JVM平臺上有單獨低延時的C4和ZGC。目前.NET平臺上沒有單獨低延時GC實現,.NET平臺這邊低延時第一是調整GCMode為GCLatencyMode.SustainedLowLatency,另外通過值類型、ref、棧上分配、非托管操作還有在關鍵路徑暫時關閉GC等方式來實現低延時。

GC演算法從提出到現在歷經了幾十年的時間,筆者也關註了一些近年的論文,其實總得來說在馮諾依曼架構體系下,GC垃圾回收演算法已經沒有顛覆性的變化。

在這個算力沒有指數級增長的時代,各個語言的GC演算法都開始趨同(C# Java Js Go等等),在沒有更創新性的GC演算法出來之前,從源頭上避免分配和Rust語言那樣的RAII管理記憶體可能是各個語言後面都會走的路。

如果大家想瞭解更多GC相關的知識,可以看下方的文章鏈接和推薦的書。

https://www.cnblogs.com/InCerry/p/dotnet-gc-workflow-1.html
https://www.cnblogs.com/InCerry/p/maoni-mem-doc.html
https://www.cnblogs.com/InCerry/p/put-a-dpad-on-that-gc.html


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

-Advertisement-
Play Games
更多相關文章
  • 1. ElasticSearch快速入門 1.1. 基本介紹 ElasticSearch特色 Elasticsearch是實時的分散式搜索分析引擎,內部使用Lucene做索引與搜索 實時性:新增到 ES 中的數據在1秒後就可以被檢索到,這種新增數據對搜索的可見性稱為“準實時搜索” 分散式:意味著可以 ...
  • 選擇結構 if 選擇結構 語法 if(布爾表達式) { //當布爾表達式為true將執行的語句 } if(布爾表達式) { //當布爾表達式為true將執行的語句 }else{ //當布爾表達式為false時執行的語句 } if(條件1) { //條件1為ture時執行的語句 }else if(條件 ...
  • 前言 當我們使用DI方式寫了很多的Service後, 可能會發現我們的有些做法並不是最優的. 獲取註入的對象, 大家經常在構造函數中獲取, 這樣也是官方推薦的方式, 但有時不是效率最高的方法. 如果在構造函數中獲取對象,那麼每次對象的初始化都會把構造函數中的對象初始化一遍, 如果某個方法只用到其中一 ...
  • Word中設置水印時,可使用預設的文字或自定義文字設置為水印效果,但通常添加水印效果時,會對所有頁面都設置成統一效果,如果需要對每一頁或者某個頁面設置不同的水印效果,則可以參考本文中的方法。下麵,將以C# 代碼為例,對Word每一頁設置不同的文字水印效果作詳細介紹。 方法思路 在給Word每一頁添加 ...
  • 在日常開發 webapi 時,我們往往會集成 swagger doc 進行 api 的文檔呈現,當api數量比較多的時候就會導致 swagger ui 上的 api 因為數量太多而顯得雜亂,今天教大家如何利用 GroupName 屬性來對 api 的 Controller 進行分組,然後利用 swa ...
  • 我們知道,如果要對一個網站進行自動化測試,可以使用Python的selenium對獲取網頁的元素進行一系列操作。同樣,對於Windows應用,可以使用C#或者AutoIt(也是一種腳本語言,相比較與C#,AutoIt更適合做Windows應用的自動化腳本)捕獲窗體句柄進行操作。 今天主要記錄一下使用 ...
  • 所在單位有消息推送的需求,整理了一下,具體要求如下: 伺服器(ASP.NET)往桌面客戶端(WPF)單向推送; 客戶端消費消息分為一次性消費(如:掃面支付結果推送)、多次消費(如:訂單推送) ClientId線上狀態其他客戶端不能再用相同的ClientId連接 一開始使用了SingalR,缺點如下: ...
  • 分享如何使用WPF 製作 Windows 屏保 WPF 製作 Windows 屏保 作者:驚鏵 原文鏈接:https://github.com/yanjinhuagood/ScreenSaver 框架使用.NET452; Visual Studio 2019; 項目使用 MIT 開源許可協議; 更多 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...