重學c#系列——c# 託管和非託管資源與程式碼相關(四)

團隊buff工具人發表於2020-07-24

前言

這是續第三節。

概況垃圾回收與我們寫程式碼的關係:

  1. 強引用和弱引用
  2. 針對共享 Web 承載優化
  3. 垃圾回收和效能
  4. 應用程式域資源監視

正文

強引用和弱引用

垃圾回收器不能回收仍在引用的物件的記憶體——這是一個強引用。它可以回收不在根表中直接或間接的託管記憶體。然而,有時可能會忘記釋放記憶體。

注意:如果物件相互引用,但是沒有在根表中引用,例如:物件A 引用物件B,B引用C,C引用A,這時候如果ABC沒有在根表中引用那麼直接會被銷燬。

補充一下根表在垃圾回收中的作用:

垃圾回收在引用的根表中找到所有引用物件,接著在引用的物件樹中查詢。

恰恰正好弱型別就沒有在根表中,然後垃圾處理器首先開刀的就是弱型別引用。可以這麼理解,理論上弱引用關聯的物件只能生存到下一次垃圾收集發生為止,但是往往不會那麼短時間,因為垃圾收集器並不是那麼容易發現這些弱引用。

強引用很好理解:

如果應用程式的程式碼可以訪問一個正由該程式使用的物件,垃圾回收器就不能回收該物件, 那麼,就認為應用程式對該物件具有強引用。

var student=new Student();

一但student離開了所在作用區域那麼引用物件就開始要被銷燬了。

所以我們有快取這個概念:

var myCache=new MyCache();
myCache.add(student);

快取的本質目的不就是為了延長垃圾回收嗎?或者說不讓其垃圾回收,持續在記憶體中。當student 超出作用區後,還是不能釋放student 的引用記憶體,因為此時物件在快取物件中引用。

即使是student=null後,那麼這個時候new Student()還是在記憶體中,因為被快取物件引用了,student在棧中的指向(無論是清空回收還是置空)控制不了釋放垃圾回收了。

那麼能不能這樣,即使被myCache引用了還是可以自動被消耗?這個時候就是弱型別登場的時候。

官方文件這樣介紹道:

弱引用允許應用程式訪問物件,同時也允許垃圾回收器收集相應的物件。 

如果不存在強引用,則弱引用的有限期只限於收集物件前的一個不確定的時間段。

使用弱引用時,應用程式仍可對該物件進行強引用,這樣做可防止該物件被收集。

但始終存在這樣的風險:垃圾回收器在重新建立強引用之前先處理該物件。

佔用大量記憶體,但通過垃圾回收功能回收以後很容易重新建立的物件特別適合使用弱引用。

假設 Windows 窗體應用中的樹狀檢視向使用者顯示層次結構複雜的選項。 如果基礎資料量很大,則使用者使用應用程式中的其他部分時,在記憶體中保留該樹會導致效率低下。

這裡有些關鍵的地方,一個體現就是:資料量很大,也就是弱型別適合佔有記憶體比較大的物件。為什麼這樣說呢?

是這樣子的,我們創造一個弱型別就是要記憶體開銷的,本身目的就是為了及時回收降低記憶體,這個時候整弱型別這不是添堵嗎?

第二個在於容易建立,如果不容易建立,那麼這個時候是時間換空間的代價有點大啊。

如何延長弱型別的生命週期呢?這時候應該使用七星燈[強型別]進行續命。

舉個例子:

var myWeaKReference=new WeakReference(new DataObject());
if(myWeaKReference.isAlive) 
{
  DataObject strongReference=myWeaKReference.Target as DataObject;
}

這時候吧弱型別給了一個強型別引用。

起碼可以續命到if結束,也就不用擔心用到一半的時候突然掛了,那麼就非常尷尬。

垃圾回收和效能

垃圾回收機制和影響到效能,最簡單的例子就是垃圾回收不好,導致了記憶體過大。

那麼我們就需要去排除是不是垃圾回收的問題。

首先第一步要確定是否是垃圾回收問題,可能出現下面的問題:

1. 引發記憶體不足異常

2. 程式佔用過多記憶體

3. 垃圾回收器回收物件的速度不夠快

4. 託管堆太零碎

5. 垃圾回收暫停時間太長

6. 第 0 代太大

7. 垃圾回收期間的 CPU 使用率太高

那麼如何去排除呢?這時候就要使用工具了。

舉個容易出現的例子:託管堆太零碎,這與我們程式碼息息相關。

我們程式碼可能會出現,下面的情況:

頻繁載入和解除安裝許多小的程式集。

與非託管程式碼互操作時,保留了太多對 COM 物件的引用。

大型暫時性物件的建立會導致大型物件堆頻繁分配和釋放堆段。

這些會導致託管堆太零碎。

如何去排查?

這時候可以使用windbg,這個工具還是很好用的。最主要是windows10現在自帶了,沒有版本不夠升級一下,對了不會windows 10還用盜版吧?

我們都是正經人,能白嫖肯定白嫖啊,不給錢就不算嫖啊。

下面是我除錯的內容:

可能有些人沒用過windbg,簡單過下流程。

開啟windbg後:

選擇對應的程式,程式很多,那麼這個時候你應該列印出來。如果除錯打包好的,直接看程式名。

Console.WriteLine(Process.GetCurrentProcess().Id);

然後開始除錯。

你需要載入sos,來檢視託管程式。

.net core 載入是這樣子的.load C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.8\sos

然後檢視一下是否載入完畢: !help.

然後你就可以查詢一些託管的東西。

在這裡!dumpheap -type Free -stat 顯示堆裡面的一些使用情況,上圖windbg就是了。

若要確定第 0 代中的可用空間,請鍵入以下命令以獲取代的記憶體使用資訊:

!eeheap -gc

當然這是一個漫長檢視過程,但是想要高效能,這又是必須的。

針對共享 Web 承載優化

我直接把文件裡面的貼過來吧,因為這很詳細了。

由於垃圾回收器保留記憶體以供將來分配,因此它提交的空間可能會超過真正所需。 可以減少此空間來適應系統記憶體負載過重的情況。 減少提交的此空間可提升效能,並將容量擴充套件為託管更多網站。

如果啟用 gcTrimCommitOnLowMemory 設定,垃圾回收器會計算系統記憶體負載,並在負載達到 90% 時進入修整模式。 除非負載下降到不到 85%,否則會一直處於修整模式。

如果條件允許,垃圾回收器可以決定 gcTrimCommitOnLowMemory 設定對當前應用沒有幫助並忽略它。

然後給了一個示例:

<?xml version="1.0" encoding="UTF-8"?>  
<configuration>  
    <runtime>  
    . . .  
    <gcTrimCommitOnLowMemory enabled="true"/>  
    </runtime>  
    . . .  
</configuration>

應用程式域資源監視

這個是什麼呢?就是說用來監控應用域監視cpu和記憶體的使用情況。裡面是這樣解釋的,說多個應用在伺服器上執行,可以監聽到哪個程式佔用過多,同時告訴我們這個arm消耗小。

這個呢,其實個人覺得現在容器化了,監控容器專門的工具了,很容易監聽到。

有四種啟動資源監控的東西。

1.可以在 CLR 啟動時啟用 ARM,具體操作是向配置檔案新增 <appDomainResourceMonitoring> 元素,並將 enabled 屬性設定為 true。 值 false(預設值)只表示不在啟動時啟用 ARM;稍後可以使用其他啟用機制之一來啟用它。

2.主機可以請求獲取 ICLRAppDomainResourceMonitor 託管介面來啟用 ARM。 成功獲取此介面後,就會啟用 ARM。

3.託管程式碼可以將靜態 AppDomain.MonitoringIsEnabled 屬性(Visual Basic 中的 Shared)設定為 true,從而啟用 ARM。 設定此屬性後,就會啟用 ARM。

4.啟動後,可以通過偵聽 ETW 事件來啟用 ARM。 使用 AppDomainResourceManagementKeyword 關鍵字啟用公共提供程式 Microsoft-Windows-DotNETRuntime 後,ARM 便會啟用,並開始丟擲所有應用域的事件。 若要將資料與應用域及執行緒相關聯,還必須使用 ThreadingKeyword 關鍵字啟用 Microsoft-Windows-DotNETRuntimeRundown 提供程式。

首先改配置檔案的放棄。然後 Windows 事件跟蹤 (ETW)是windows的。去呼叫api感覺麻煩。應用程式域資源監視非常重要,但是還是找個第三方監聽吧。

前面一直介紹託管資源,後面介紹非託管資源,整理了一點點。

注:上述純屬個人的整理,如有誤,望指出。

相關文章