為什麼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
  • 移動開發(一):使用.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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...