C#學習筆記(十四):GC機制和弱引用


垃圾回收(GC)

垃圾回收即Garbage Collector,垃圾指的是內存中已經不會再使用的對象,通過收集釋放掉這些對象占用的內存。

GC以應用程序的root為基礎,遍歷應用程序在Heap上動態分配的所有對象,通過識別它們是否被引用來確定哪些對象是已經死亡的、哪些仍需要被使用。已經不再被應用程序的root或者別的對象所引用的對象就是已經死亡的對象,即所謂的垃圾,需要被回收。

關於C#使用的垃圾回收算法可以點擊這里查看。

析構函數

析構函數會在GC執行清楚當前對象時被調用,可以在析構函數中執行一些釋放方法。

為了優化GC算法,微軟使用了“代”的概念,介紹如下:

堆里面總共有3代。

譬如,當程序運行時,有對象需要存儲在堆里面,GC就會創建第1代(假設空間大小為256K),對象就會存儲在第0代里面,當程序繼續運行,運行到第0代的大小不足以存放對象,這時候就就會創建第1代(假設空間為10M),GC就會把第0代里面的“垃圾對象”清理掉,把“活着”的對象放在第1代,這時候第0代就空了,用於存放新來的對象,當第0代滿了的時候,就會繼續執行以上操作,隨着程序的運行,第1代不能滿足存放要求,這時候就會創建第2代,清理方式如上相同。

我們來看一個例子:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test test = new Test();
10             
11             //對象會被分配到第 0 代
12             Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test));
13             //回收對象, 這時 test 會被分配到第 1 代
14             GC.Collect();
15             Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test));
16             //回收對象, 這時 test 會被分配到第 2 代
17             GC.Collect();
18             Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test));
19             //回收對象, 最多只有3個代, 所以 test 還在第 2 代
20             GC.Collect();
21             Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test));
22 
23             //斷開引用, 對象會被回收
24             test = null;
25             //回收對象, test 會被回收並調用析構函數
26             GC.Collect();
27 
28             Console.Read();
29         }
30     }
31 
32     public class Test
33     {
34         ~Test()
35         {
36             Console.WriteLine("Test被回收了!");
37         }
38     }
39 }

運行結果如下:

1 test對象所在的代:0
2 test對象所在的代:1
3 test對象所在的代:2
4 test對象所在的代:2
5 Test被回收了!

何時GC

.Net何時執行GC在《C#高級編程》書中也只是簡單的一句“垃圾回收會在運行庫認為需要他時運行。”帶過,總體而言,GC運行策略已經被微軟進行優化過了,我們不需要過多的關心即可。

托管對象和非托管對象

在C#中,很大部分的對象都是托管對象,托管對象的釋放直接由GC來處理,但也存在部分非托管對象,這些對象GC是不能對其進行自動回收的

非托管對象

即不受運行時管理的資源對象(如窗口句柄 (HWND)、數據庫連接等)。

非托管的對象如下:ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,Timer,Tooltip ,文件句柄,GDI資源,數據庫連接等等資源。

比如:當我們使用一個System.IO.StreamReader的一個文件對象,必須顯示的調用對象的Close()方法關閉它,否則會占用系統的內存和資源,而且可能會出現意想不到的錯誤。

Finalize

當我們聲明一個析構函數時實際上編譯器就會自動添加下面的代碼:

1 ~Test()
2 {
3     try{
4         Finalize();
5     }finally{
6         base.Finalize();
7     }
8 }

如果在派生類中不存在析造函數,卻重載了基類的終結器:

1 protected override void Finalize()
2 {
3 }

垃圾回收時,GC找不到構造函數,會直接調用終結器。

如果沒有顯示釋放資源時,GC時可以靠該方法進行隱式的釋放資源。

Dispose

相對於重寫Finalize方法,實現IDisposable接口來進行顯示的釋放資源是更好的一種方式。

我們只需要實現IDisposable接口即可,我們看看官網提供的常規寫法:

 1 using System;
 2 
 3 class BaseClass : IDisposable
 4 {
 5    // Flag: Has Dispose already been called?
 6    bool disposed = false;
 7 
 8    // Public implementation of Dispose pattern callable by consumers.
 9    public void Dispose()
10    { 
11       Dispose(true);
12       GC.SuppressFinalize(this);           
13    }
14 
15    // Protected implementation of Dispose pattern.
16    protected virtual void Dispose(bool disposing)
17    {
18       if (disposed)
19          return; 
20 
21       if (disposing) {
22          // Free any other managed objects here.
23          //
24       }
25 
26       // Free any unmanaged objects here.
27       //
28       disposed = true;
29    }
30 }

https://msdn.microsoft.com/zh-cn/library/system.idisposable(v=vs.110).aspx

https://msdn.microsoft.com/zh-cn/library/fs2xkftw(v=vs.110).aspx

弱引用

我們都知道當一個對象存在一個或多個引用時,GC是不會釋放該對象的,如下:

Object obj = new Object();

這種引用稱為強引用。

但是我們可以想一下,還有一種情況是對象稍后可能被使用,但不是很確定是否會使用時,就可以使用弱引用了。

弱引用可以理解為:如果一個對象只存在一個或多個弱引用而沒有強引用時,則GC可以對其進行垃圾回收。

那么在C#中該如何使用弱引用呢?

WeakReference和WeakReference<T>

C#提供了兩個類來實現弱引用的功能,我們只需要將對象裝入該類的實例中,則可以理解為為這個對象添加了一個弱引用,下面給出幫助文檔地址:

WeakReference:https://msdn.microsoft.com/zh-cn/library/system.weakreference(v=vs.110).aspx

WeakReference<T>:https://msdn.microsoft.com/zh-cn/library/gg712738(v=vs.110).aspx

我們再看一個例子:

 1 using System;
 2 
 3 namespace Study
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             //強引用
10             Test test = new Test();
11             //弱引用 1
12             WeakReference weak1 = new WeakReference(test);
13             //弱引用 2
14             WeakReference<Test> weak2 = new WeakReference<Test>(test);
15 
16             //存在強引用不會被回收
17             GC.Collect();
18 
19             //注意 temp 也是一個強引用
20             Test temp;
21 
22             Console.WriteLine("weak1: " + weak1.IsAlive + ", weak2: " + weak2.TryGetTarget(out temp));
23 
24             //解除所有強引用
25             test = null;
26             temp = null;
27 
28             //沒有強引用會被回收
29             GC.Collect();
30 
31             Console.WriteLine("weak1: " + weak1.IsAlive + ", weak2: " + weak2.TryGetTarget(out temp));
32 
33             Console.Read();
34         }
35     }
36 
37     public class Test
38     {
39         ~Test()
40         {
41             Console.WriteLine("Test被回收了!");
42         }
43     }
44 }

結果如下:

1 weak1: True, weak2: True
2 weak1: False, weak2: False
3 Test被回收了!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM