日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

编写高性能 .NET 代码 第二章:垃圾回收 基本操作

發布時間:2023/12/4 asp.net 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 编写高性能 .NET 代码 第二章:垃圾回收 基本操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

基本操作

垃圾回收的算法細節還在不斷完善中,性能還會有進一步的提升。下文介紹的內容在不同的.NET版本里會略有不同,但大方向是不會有變動的。

在.net進程里會管理2個類型的內存堆:托管和非托管。本地代碼申請的,以及由CLR申請的都是非托管內存,使用Windows API 的 VirtualAlloc 方法進行申請。CLR里分配的托管對象則分配在托管堆里,這些對象可以被垃圾回收處理。

在托管堆里有還進一步分為小對象對和大對象堆(LOH)。每個對象類型都有自己的一段堆內存段。每段的大小根據你的配置或者硬件配置有關,對于一個大型的應用,一個內存段可到幾百M。

小對象還可以進一步分為3個世代。0代和1代總是在一個內存段里,2代則可以跨越多個內存段。包含0代和1代的內存段稱為暫存段。
下圖是堆的圖形,分別是A段和B段

A段是小對象堆,B段是大對象堆。2代和1代開始只有幾個字節大小,因為他們到目前為止是空的。

在小對象堆里分配對象存在一個3世代的生命周期,CLR在小對象堆里分配小于85000字節的對象(8.5k)。0代內存通常在段內存的結尾開始分配。這就是為什么前面看到的.NET內存分配得很快。如果快速分配失敗,則在0代邊界范圍里找一個合適的分配地址。如果也沒有合適的位置,則分配器會擴大0代的在內存段里邊界范圍。如果擴展范圍時超過了內存段的范圍,則會觸發垃圾回收。

對象創建后都是0代。只要對象還存在,在GC時會將它們標記到下一代。0代和1代稱為臨時集合。

當觸發GC時,可能會同時觸發壓縮,在這種情況下,GC會將對象移動到另外一個內存地址里,釋放當前段的內存空間。如果沒有觸發壓縮,也僅僅只是重新劃分邊界。在一些沒有觸發過壓縮的GC后,結果如下圖:

雖然對象沒有移動,但邊界有重新劃分。

壓縮可以發生在任何一代的GC里,這是一個相當耗時的過程,因為它需要重新更新對象的引用關系,這需要暫停所有的線程操作。正因為代價高,壓縮操作只會在必要的時候才會進行。

一旦對象到達2代,在剩下的生命周期里會一直保持。這并不意味著2代對象會一直存在,如果所有的2代對象被回收,并且整段內存里都沒有對象,則這段內存可以被操作系統回收,或者作為其他內存段的附屬段。這通常出現在全回收階段。

那么對象的活著是什么概念呢?在GC時可以通過任何已知的root節點到達對象,能找到對象的引用關系,就說明這個對象還活著。root節點可以是程序里的某個靜態變量,線程里正在執行的方法(指局部對象),GC句柄(被pinned的句柄)以及在finalizer queue里的對象。請注意,如果你的對象在2代,并且可以被回收,但你在做0代GC時,它也不會被回收。他們需要等到一個完整回收時才會被干掉。

如果0代對象已經填充滿一個內存段,再做壓縮也不能獲得足夠空間時,gc會重新分配一個新的內存段。新的段將用于放置新分配的1代和0代對象。而之前段的對象都將轉為2代對象。所有的0代對象轉為1代,1代對象同樣降為2代對象(因為不需要復制所以很容易實現)。這個新的內存段看起來如下:

如果2代內存不斷增加,那么它可以存放在多個內存段里。LOH也一樣可以跨越多個內存段存放。但不管有多少個內存段,0代和1代對象始終在一個內存段里。了解到這些知識對后面的學習會很有幫助。大對象堆使用另外一種規則。通常來說一些字符串或者數組,大小在8500直接以上會自動分配到LOH堆里,它不會走上面的代紀模型。出于性能考慮,LOH分配的對象不會在回收的時候做壓縮過程,但從.net4.5.1開始,你可以按需做壓縮了。就行2代對象那樣,當對象不再LOH里需要時,你還是可以回收它使用的內存空間,但稍后我們會說到,在垃圾回收里,最理想的狀態是不要將對象創建到大對象堆上。
在LOH里,分配器都是使用空閑列表的方式來尋找最合適的位置來分配,在本章的后面將探索一些技術來減少在大數據堆上減少內存碎片。

垃圾回收特定代時,會順帶回收它下面的代。例如回收1代時,會把0代的也回收。如果是2代,那么就是把所有的都回收了(包括LOH里的)。如果是0代或者1代回收時,程序會暫停執行到GC過程結束。如果是回收2代,這一部分操作可以可能會在另外一個后臺線程里執行,這取決于系統配置。

垃圾回收分為4個階段:

  • 暫停--所有托管線程需要在回收開始前暫停。

  • 標記--從每個root節點開始,回收器會對每個對象的引用做標記

  • 壓縮--移動內存對象,并重新修改引用路徑以便釋放內存碎片。它通常發生在小對象堆上,并在系統認為需要的時候進行,你不能手動控制。在大對象堆上,壓縮不會自動發生,但你可以配置垃圾回收器按需壓縮他。

  • 恢復--托管線程回復執行

  • 標記階段實際上不需要遍歷堆上的每個對象,它只會處理需要回收的堆。舉個栗子,做0代收集時只會考慮0代的對象,做1代收集時則會標記0代和1代的對象。在做2代或者一次完整回收時,才需要遍歷每一個活著的對象,當然這個開銷就比較高了。另外需要考慮的是,一個高代的對象可能是低代對象的root節點,這會導致在做遍歷時,也會遍歷相關的高代對象,當然這個開銷會比全回收階段時小一些。

    上面描述的過程會導致以下問題。
    首先,垃圾回收所消耗的時間,取決于當前還活著的對象數量,而不是已經分配出去的數量。這就意味著,如果分配了100w個對象在一個root節點上,只要下次GC前,你把它與root切斷引用關系,這100w個對象對你的回收耗時不會造成太大影響。
    其次,垃圾回收的頻率取決于特定的一代里分配了多少內存。一旦超過內部的一個閾值,GC將回收這一代的對象。這個過程GC會根據你的程序做動態調整。如果在某代的回收卓有成效(回收了很多對象),則在這一代的回收會頻繁觸發,反之則減少。另外一個觸發因素就是你的程序在電腦里的可用內存。如果可用內存低于某個閾值,GC也會頻繁發生,用來減少堆的總體體積。

    從上面的描述里,你可能覺得GC已經超過了你的控制范圍。但這其實已經離真相不遠了。最簡單的優化方式就是,你可以通過控制內存的分配模式來達到。你在了解GC是如何工作后,你可以根據分配速率,對象的生命周期,來選擇合適的配置。

    相關文章:

    • [翻譯]編寫高性能 .NET 代碼 第一章:性能測試與工具 -- 選擇什么來衡量

    • [翻譯]編寫高性能 .NET 代碼 第一章:性能測試與工具 -- 平均值 vs 百分比

    • [翻譯]編寫高性能 .NET 代碼 第一章:工具介紹 -- Visual Studio

    • 編寫高性能 .NET 代碼 第一章:工具介紹 -- Performance Counters(性能計數器)

    • 編寫高性能 .NET 代碼 第二章:垃圾回收

    原文地址:http://www.cnblogs.com/yahle/p/6552457.html


    .NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

    總結

    以上是生活随笔為你收集整理的编写高性能 .NET 代码 第二章:垃圾回收 基本操作的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。