重學c#系列——c# 託管和非託管資源與代碼相關(四)
- 2020 年 7 月 24 日
- 筆記
- c# 基礎(回憶錄)
前言
這是續第三節。
概況垃圾回收與我們寫代碼的關係:
- 強引用和弱引用
- 針對共享 Web 承載優化
- 垃圾回收和性能
- 應用程序域資源監視
正文
強引用和弱引用
垃圾回收器不能回收仍在引用的對象的內存——這是一個強引用。它可以回收不在根表中直接或間接的託管內存。然而,有時可能會忘記釋放內存。
注意:如果對象相互引用,但是沒有在根表中引用,例如:對象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感覺麻煩。應用程序域資源監視非常重要,但是還是找個第三方監聽吧。
結
前面一直介紹託管資源,後面介紹非託管資源,整理了一點點。
註:上述純屬個人的整理,如有誤,望指出。