在很多的場合我都遇到過一些群友提這樣的一些問題:
首先給大家介紹一下R大,R大網名叫RednaxelaFX,南京大學畢業巨佬,主攻高階程式語言虛擬機器器的設計與實現,對於C# .NET CLR有非常深入的研究和了解。先後加入阿里、Oracle、Azul System從事JVM虛擬機器器開發的工作(HotSpot JVM and Zing JVM)。現在在Databricks從事Spark開發工作。
iteye部落格:https://www.iteye.com/blog/user/rednaxelafx
知乎主頁: https://www.zhihu.com/people/rednaxelafx
GitHub: https://github.com/rednaxelafx
RednaxelaFX的回答 - 知乎 https://www.zhihu.com/question/52900051/answer/132583244
這裡R大主要就CLR上幾種調優方式和為什麼CLR不需要xmx、xmn等引數給出了原因
因為題主您就是沒聽說過 >_<
假定題主說的是下面三個層面的調優的頭兩種情況:
(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有哪些先進之處? - 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規範
當前最新的版本是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檔案
CIL(Common Intermediate Language) / MSIL vs Java位元組碼
CLI裡藏的私貨:Assembly採用PE(Portable Executable)格式。PE是Windows上原生的可執行檔案格式。
CLR與Windows的整合
CLR 與 一些JVM實現之間有對應物的
再來給大家介紹一下hez2010大佬,今年剛從中山大學畢業,對於C++、.NET、C#、Rust等平臺語言都有深入的研究,經常在部落格園看文章的小夥伴應該早就看過hez佬的博文。他是.NET Runtime開源專案的持續貢獻者、Microsoft Student Partner、可以說年少有為。
個人部落格:https://hez2010.com/
部落格園:https://www.cnblogs.com/hez2010/
知乎:https://www.zhihu.com/people/hez2010
GitHub:https://github.com/hez2010
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:
所以要是真的對自帶 GC 不滿的話,可以考慮自己寫 GC 來用。
兩位大佬回答的比較好,基本原因給說的比較明白了,在我看來兩者的比較如下:
-xmx
、-xmn
、-xms
這些選項。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