.Net Discovery系列文章阅读索引--带你探索未知的.Net世界
? .Net Discovery系列文章是講述.Net平臺機制的文章,目前已有12篇,分別講述了.Net垃圾收集、實時編譯、字符串等部件的機制,現在推出1周年之際總結文章閱讀索引,希望對大家有所幫助。
?
? string--.Net平臺永恒的話題。這是一種比較特殊的數據類型,它是基元類型,也是引用類型,在編譯以及運行時,.Net平臺都對它做了一些優化工作,正是這些優化工作有時會迷惑編程人員,使string看起來難以琢磨,這篇文章分上下兩章,共四節,來講講關于string的陌生一面。
| ? 重點回顧:? ? 在C#中,如果用new關鍵字實例化一個類,對應是由IL指令newobj來完成的;而創建一個字符串,則由ldstr指令完成,看到ldstr指令,我們即可認為,IL希望創建一個新的字符串 。 ? 從某些方面講,正是字符串的恒定性,才造就了字符串的駐留機制,也為字符串的線程同步工作大開方便之門(同一個字符串對象可以在不同的應用程序域中被訪問,所以駐留的字符串是進程級的,垃圾回收不能釋放這些字符串對象,只有進程結束這些對象才被釋放)。 ? 在.Net中處理字符串時,有一個很重要的機制,叫做字符串駐留機制。由于string是編程中用到的頻率較高的一種類型,CLR對相同的字符串,只分配一次內存。CLR內部維護著一塊特殊的數據結構,我們叫它字符串池,可以把它理解成是一個HashTable,這個HashTable維護著程序中用到的一部分字符串,HashTable的Key是字符串的值,而Value則是字符串的內存地址。一般情況下,程序中如果創建一個string類型的變量,CLR會首先在HashTable遍歷具有相同Hash Code的字符串,如果找到,則直接把該字符串的地址返回給相應的變量,如果沒有才會在內存中新建一個字符串對象。 |
?
? 通過上一篇文章,大家會了解字符串駐留機制、恒定性、常量池等特性,這篇文章通過10個例子,來為大家講解string,同時如果你自己對string的了解程度,有足夠的信心,那么就來讀一下這篇文章,試試做一下10個例子,檢測一下自己有多“牛”。
| ? 重點回顧: ? 代碼九: ? ? ? ??? string a = "a"; ??? string b = new string('a', 1); ??? Response.Write(a.Equals(string.Intern(b))); ??? Response.Write(ReferenceEquals(a, string.Intern(b))); ? 輸出:True (Equals比較值,無論是否Intern都會相同) ? True (ReferenceEquals比較字符串對象的引用,Intern已經將b駐留至字符串池內) ? 代碼十: ??? string a = "str"; ??? string b = "str_2".Substring(0,3); ??? Response.Write(a.Equals(b)); ??? Response.Write(ReferenceEquals(a, b)); ? 輸出:True (Equals比較值,a與c的值相同) ? False (ReferenceEquals比較字符串對象的引用,Substring操作產生了新的字符串對象) ? 此段代碼產生了3個string對象,是哪3個呢?如果你不明白,還是從頭再看一遍吧! |
?
? 這篇文章將全面的為大家介紹.Net 垃圾收集的運行方式、算法,以及與垃圾收集相關的關鍵方法。 說到垃圾收集機制,很少有人知道,垃圾收集并不是伴隨Java出現的,早在1958年,圖林獎得主John發明的Lisp語言就已經提供了GC的功能,這是GC的第一次出現,是思想的一次閃光!而后,1984年Dave Ungar發明的Small talk語言第一次正式采用了GC機制。
? 這篇文章將重點為大家介紹.Net垃圾收集器、代齡、策略引擎,并結合Windbg為大家展現一個有趣的機制平臺。
| ? 重點回顧: ? .Net中采用了一種叫做“標記與清除(Mark Sweep)”算法來完成上述任務。 “標記與清除”算法,顧名思義,這種算法有兩個本領: ? “標記”本領——垃圾的識別:從應用程序的root出發,利用相互引用關系,遍歷其在Heap上動態分配的所有對象,沒有被引用的對象不被標記,即成為垃圾;存活的對象被標記,即維護成了一張“根-對象可達圖”。 ? 其實,CLR會把對象關系看做“樹圖”,無疑,了解數據結構的同學都知道,有了“樹圖”的概念,會加快遍歷對象的速度。 ? 檢測、標記對象引用,是一件很有意思的事情,有很多方法可以做到,但是只有一種是效率最優的,.Net中是利用棧來完成的,在不斷的入棧與出棧中完成檢測:先在樹圖中選擇一個需要檢測的對象,將該對象的所有引用壓棧,如此反復直到棧變空為止。棧變空意味著已經遍歷了這個局部根(或者說是樹圖中的節點)能夠到達的所有對象。樹圖節點范圍包括局部變量(實際上局部變量會很快被回收,因為它的作用域很明顯、很好控制)、寄存器、靜態變量,這些元素都要重復這個操作。一旦完成,便逐個對象地檢查內存,沒有標記的對象變成了垃圾。 ? “清除”本領——回收內存:啟用Compact算法,對內存中存活的對象進行移動,修改它們的指針,使之在內存中連續,這樣空閑的內存也就連續了,這就解決了內存碎片問題,當再次為新對象分配內存時,CLR不必在充滿碎片的內存中尋找適合新對象的內存空間,所以分配速度會大大提高。但是大對象(large object heap)除外,GC不會移動一個內存中巨無霸,因為它知道現在的CPU不便宜。通常,大對象具有很長的生存期,當一個大對象在.NET托管堆中產生時,它被分配在堆的一個特殊部分中,移動大對象所帶來的開銷超過了整理這部分堆所能提高的性能。 |
? 這一篇主要講了GC相關的重要方法。主要包括終止隊列(Finalization Queue)與可達隊列(Freachable Queue)、復生(Resurrection)、弱引用(WeakReference)、策略引擎、Dispose()、GC.Collect()、析構函數(Finalize()等知識點。
| ? 重點回顧: ? 首先要了解與Finalize相關的兩個隊列:終止隊列(Finalization Queue)與可達隊列(Freachable Queue),這兩個隊列存儲了一組指向對象的指針。 ? 當程序中在托管堆上分配空間時(new),如果該類含有析構函數,GC將在Finalization Queue中添加一個指向該對象的指針。 ? 在GC首次運行時,會在已經被確認為垃圾的對象中遍歷,如果某個垃圾對象的指針被Finalization Queue包含,GC將這個對象從垃圾中分離出來,將它的指針儲存到Freachable Queue中,并在Finalization Queue刪除這個對象的指針記錄,這時該對象就不是垃圾了——這個過程被稱為是對象的復生(Resurrection)。當Freachable Queue一旦被添加了指針之后,它就會去執行對象的Finalize()方法,清除對象占用的資源。 ? 當GC再次運行時,便會再次發現這個含有Finalize()方法的垃圾對象,但此時它在Finalization Queue中已經沒有記錄了(GC首次運行時刪掉了它的Finalization Queue記錄),那么這個對象就會被回收了。 |
? JIT--實時編譯機制是.Net平臺的又一亮點,這個文章將分為上下兩節,從運行原理、機制等方面,結合WinDbg為大家詳細的講解JIT方面的知識。關鍵字:JIT MSIL 元數據 方法表 托管模塊 本地映像。
| ? 重點回顧: ? JIT是運行時的一個重要職責模塊,它將IL轉換為本地CPU指令,從上圖可以看出,也許你不敢相信,即時編譯這個過程是在運行時發生的,這會不會對性能產生影響呢?事實上答案是雖然是肯定的,但這種開銷物有所值: ? 1. JIT所造成的性能開銷并不顯著。 ? 2. JIT遵循計算機體系理論中兩個經典理論:局部性原理與8020原則。局部性原理指出,程序總是趨向于使用最近使用過的數據和指令,這包括空間的和時間的,將局部性原理引申可以得出,程序總是趨向于使用最近使用過的數據和指令,以及這些正在使用的數據和指令臨近的數據和指令(憑印象寫的,但不曲解原意);而8020原則指出,系統大多數時間總是花費80%的時間去執行那20%的代碼。 ? 根據這兩個原則,JIT在運行時會實時的向前、后優化代碼,這樣的工作只有在運行時才可以做到。 ? 3. JIT只編譯需要的那一段代碼,而不是全部,這樣節約了不必要的內存開銷。 ? 4. JIT會根據運行時環境,即時的優化IL代碼,即同樣的IL代碼運行在不同CPU上,JIT編譯出的本地代碼是不同的,這些不同代碼面向自己的CPU做出了優化。 ? 5. JIT會對代碼的運行情況進行檢測,并對那些特殊的代碼經行重新編譯,在運行過程中不斷優化。 |
? 這一篇文章主要講了JIT一些實例,結合Windbg,對代碼進行運行時監控,并通過Windbg的反饋向大家展示運行時編譯的過程。
| ? 重點回顧: ? 回車后注意高亮區域的信息:
? 圖8 JIT前A類型的信息 ? 高亮區域顯示的是“<not loaded yet>”,這說明雖然運行和程序,但未點擊按鈕時,A類型未被JIT,因為它還沒有入口地址。這一點體現了即時、按需編譯的思想。 ? 同樣,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令會得到同樣的結果。 ? 好,現在做第4步操作,Detach Debuggee進程,并回到程序中點擊“GO”按鈕
圖9 點擊按鈕 ? 第五步 重新附加進程(參考第一步),這時程序已經調用了new A().a1()方法,并重新執行命令!name2ee *!JITTester.A ,注意高亮部分
圖10 JIT后A類型的信息 ? 和圖8中的信息比較,圖10中的方法表地址已經變為JIT后的內存地址,這時圖4中的Stub槽將被一條強制跳轉語句替換,跳轉目標與該地址有關。這一點說明JIT在大多情況下,只編譯一次代碼。 |
??? 新年伊始,該文章是博客園2010年的第一篇文章,感興趣的同學可以注意一下該文章的發布日期,是2010年1月1日1秒。
??? 本文分三節為大家深入介紹.Net GC的完整收集(Full GC)機制 、GC工作模式以及.Net 4.0中GC的特性方法。
| ? 重點回顧: ? Workstation GC without Concurrent: 用于單CPU的服務器,策略引擎會調節GC工作頻率,使用掛起->查找與標記->壓縮->恢復的流程進行GC工作。 ? Workstation GC with Concurrent: Concurrent GC與Non Concurrent GC模式相比,有著更敏捷的反應速度,Winform應用程序和Windows services?? 服務程序默認采用這種模式,單CPU機器上只能使用workstation GC方式,默認為 Workstation GC with Concurrent。 在這種模式下,第0、1代的收集仍然是要暫時掛起應用程序的,只有在收集第2代時,才會并行處理,這種并行收集是利用多CPU 對Full GC進行并行處理,具體原理是將Full GC過程切分成多個短暫子過程對線程進行凍結,在線程凍結時間之外,應用程序仍然可 以正常運行。這主要通過將0代空間設置的很大,使Full GC時,CLR仍然能夠在0代中進行內存分配,如果Full GC時0代內存也已用盡,那么應用程序將被掛起,等待Full GC的完成。 ? Server GC: 用于多CPU的服務器,這種GC模式有著很高的性能和效率。這種模式下,CLR為每個CPU創建一個專用的GC線程,每個CPU可以獨立的為相應的? heap執行GC操作,這些GC線程是以非并發的形式工作的,收集工作與線程正常工作不能同時進行,這就是說第0、1、2代的收集都會掛起應用線程。 ? 在.Net 4.0中,有一種新的垃圾收集機制,叫做后臺收集。這種機制以concurrent GC為基礎的,如上文所講,Workstation GC with Concurrent模式中,在Full GC過程時,CLR仍然能夠在0代中進行內存分配,如果Full GC時0代內存也已用盡,那么應用程序將被掛起,等待Full GC的完成。 這個過程在后臺收集機制中是這樣工作的,在進行Full GC時可以同時進行第0、1代收集,并且后臺收集是一個獨立線程完成的,這個進程任務優先級低于第0、1代收集,如果在后臺收集中需要對第0、1代收集,后臺收集將會等待第0、1代收集完成后再進行工 作,當然第0、1代收集是需要短暫掛起應用的。 ? 后臺收集還會根據策略引擎的指示,動態調節第0、1代的容量,減少前臺收集(第0、1代收集)次數。 |
? 本文是《.Net Discovery》系列文章(一)的勘誤版。
| 重點回顧: ? 所以,第三行C#代碼(a = "str_2";)的樣子看起來是在修改變量a的舊值"str_1",但實際上是創建了一個新的字符串"str_2",然后將變量a的指針指向了"str_2"的內存地址,而"str_1"依然在內存中沒有受到任何影響,所以變量b的值沒有任何改變---這就是string的恒定性,同學們,一定要牢記這一點,在.Net中,string類型的對象一旦創建即不可修改!包括ToUpper、SubString、Trim等操作都會在內存中產生新的字符串。 |
? 本文是《.Net Discovery》系列文章(二)的勘誤版。
| 重點回顧: ? 代碼二: ??? string a = "str_1str_2"; ??? string b = "str_1"; ??? string c = "str_2"; ??? string d = b + c; ??? Response.Write(a.Equals(d)); ??? Response.Write(ReferenceEquals(a, d)); ? 輸出:True(Equals比較字符串對象的值) ? False(ReferenceEquals比較字符串對象的引用,由于變量d的值為變量連接的結果,字符串駐留機制無效) ? 代碼三: ??? string b = "str_1" + "str_2"; ??? Response.Write(a.Equals(b)); ??? Response.Write(ReferenceEquals(a, b)); ? 輸出:True(Equals比較字符串對象的值) ? True (ReferenceEquals比較字符串對象的引用,由于變量b的值為常量連接的結果,字符串駐留機制有效。如果變量b的值由“常量+變量”的方式得出,則字符串駐留無效) |
? 轉眼間《.Net Discovery》系列文章已經推出1年了,本文為該系列的第10-13篇文章,在本文中將對以前所講的.Net平臺知識做一個小小的總結與機制分析,引出并重點介紹這些機制對程序性能的影響與改進建議。 本文將分為四部分,分別講述了:垃圾回收機制、即時編譯機制、異常處理機制、字符串駐駐留機制的原理與性能改進建議。
? 本文主要介紹垃圾回收機制對系統性能的影響分析。
| 重點回顧: ? 垃圾收集器一般將托管堆中的對象分為3代,這可以通過調用GC.MaxGeneration得知,對象按照存在時間長短進行分代,最短的分在第0代,最長的分在第2代,第2代中的對象往往是比較大的,第二代空間被稱作Large Object Heap,對于2代對象的回收,與第0、1代回收方式相比最大的不同在于,沒有了指針移動的壓縮過程。 ? 如下圖,第一次GC時,左邊第一列A-F表示內存中的對象,位于淺藍色 區域,經過Mark后,ACDF標記為可用,Sweep過程清除了BE,Compact過程移動了ACDF,使之位于連續存儲區域中;第二次使用綠色做標記;第三次GC使用藍色表示標記;可以看出第三次GC過程沒有了指針移動的壓縮過程。
圖1 對象的回收 |
? 轉眼間《.Net Discovery》系列文章已經推出1年了,本文為該系列的第10-13篇文章,在本文中將對以前所講的.Net平臺知識做一個小小的總結與機制分析,引出并重點介紹這些機制對程序性能的影響與改進建議。 本文將分為四部分,分別講述了:垃圾回收機制、即時編譯機制、異常處理機制、字符串駐駐留機制的原理與性能改進建議
? 本文主要介紹即時編譯機制對系統性能的影響分析。
| 重點回顧: ? 運行時,操作系統會根據托管模塊中各種頭信息,裝載相應的運行時框架,Load()被加載,由于是第一次加載,這會觸發對Load()的即時編譯,JIT會檢測Load()中引用的所有類型,并結合元數據遍歷這些類型中定義的所有方法實現,并用一個特殊的HashTable(僅用于理解)儲存這些類型方法與其對應的入口地址(在未被JIT前,這個入口地址為一個預編譯代理(PreJitStub),這個代理負責觸發JIT編譯),根據這些地址,就可以找到對應的方法實現。 ? 在初始化時,HashTable中各個方法指向的并不是對應的內存入口地址,而是一個JIT預編譯代理,這個函數負責將方法編譯為本地代碼。注意,這里JIT還沒有進行編譯,只是建立了方法表!
圖2方法表、方法描述、預編譯代理關系 ? 圖2中所示的MS核心引擎指的是一個叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一個橋接DLL,連同mscorwks.dll主要完成以下工作: ? 1. 查找程序集中包含的對應類型清單,并調用元數據遍歷出包含的方法。 ? 2. 結合元數據獲得這個方法的IL。 ? 3. 分配內存。 ? 4. 編譯IL為本地代碼,并保存在第3步所分配的內存中。 ? 5. 將類型表(就是指上文中提到的HashTable)中方法地址修改為第3步所分配的內存地址。 ? 6. 跳轉至本地代碼中執行。 ? 所以隨著程序的運行時間增加,越來越多的方法的IL被編譯為本地代碼,JIT的調用次數也會不斷減少。 |
? 轉眼間《.Net Discovery》系列文章已經推出1年了,本文為該系列的第10-13篇文章,在本文中將對以前所講的.Net平臺知識做一個小小的總結與機制分析,引出并重點介紹這些機制對程序性能的影響與改進建議。 本文將分為四部分,分別講述了:垃圾回收機制、即時編譯機制、異常處理機制、字符串駐駐留機制的原理與性能改進建議
?
? 本文主要介紹異常處理機制、字符串駐駐留機制對系統性能的影響分析。
| 重點回顧: ? .Net 中基本的異常捕獲與處理機制是由try…catch…finally塊來完成的,它們分別完成了異常的監測、捕獲與處理工作。一個try塊可以對應零個或多個catch塊,可以對應零個或一個finally塊。不過沒有catch的try似乎沒有什么意義,如果try對應了多個catch,那么監測到異常后,CLR會自上而下搜索catch塊的代碼,并通過異常過濾器篩選對應的異常,如果沒有找到,那么CLR將沿著調用堆棧,向更高層搜索匹配的異常,如果已到堆棧頂部依然沒有找到對應的異常,就會拋出未處理的異常了,這時catch塊中的代碼并不會被執行。所以距離try最近的catch塊將最先被遍歷到。 ? 最后三行的每一個Item被稱作Exception Handing Clause,EHC組成Exception Handing Table,EHT與正常代碼之間由ret返回指令隔開。 ? 可以看出,FormatException排列在EHT的第一位。 ? 當代碼成功執行或反之而返回后,CLR會遍歷EHT: ? 1. 如果拋出異常, CLR會根據拋出異常的代碼的“地址”找到對應的EHC(IL_0001 to IL_0010為檢測代碼的范圍),這個例子中CLR將找到2條EHC, FormatException會最先被遍歷到,且為適合的EHC。 ? 2. 如果返回的代碼地址在IL_0001 to IL_0029內,那么還會執行finally handler 即IL_0029 to IL_0033中的代碼,不管是否因成功執行代碼而返回。 ? 事實上,catch與finally的遍歷工作是分開進行的,如上文所言,CLR首先做的是遍歷catch,當找到合適的catch塊后,再遍歷與之對應finally;而且這個過程會遞歸進行至少兩次,因為編譯器將C#的try…catch…finally翻譯成IL中的兩層嵌套。 ? 當然如果沒有找到對應的catch塊,那么CLR會直接執行finally,然后立即中斷所有線程。Finally塊中的代碼肯定會被執行,無論try是否檢測到了異常。 |
總結
以上是生活随笔為你收集整理的.Net Discovery系列文章阅读索引--带你探索未知的.Net世界的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Intel Developer Foru
- 下一篇: Flash cs5 初试