文章目錄
一、資源
資源是一個反覆被利用的術語。術語”資源“的一個用法是本地化。在本地化中,資源用於翻譯文本和圖像。”資源“的另一個用法用於主題:使用託管和非託管的資源——儲存在託管和本機堆中的資源。儘管垃圾收集器釋放儲存在託管堆中的託管對象,但不釋放本機堆中的對象。必須由開發人員自己釋放他們。
二、後臺內存管理
雖然C#編程不需要擔心具體的內存管理,但理解程序如何在後臺管理內存有助於提高性能和速度。
1.值數據類型
windows使用一個虛擬尋址系統,把程序的可用內存地址映射到真實的內存地址上。實際上每個進程都使用4GB的內存(32位處理器)。這個4GB包含了程序的所有代碼,dll和變量空間等。被稱爲虛擬地址空間或虛擬內存。
4GB中的每一個存儲單元都是從0往上排序。要訪問內存中的某個值就要提供表示這個空間的一個數字。編譯器將便於人們理解的變量轉換爲處理器可以理解的內存地址。
在虛擬內存中,有一個區域稱爲棧。工作原理,先進後出。(如圖)
{
int a;
{
int b;
}
}
在一段程序中先定義a再定義b,程序結束時會先釋放b,再釋放a,b的生命週期完全包含在a中。同時b在另一個代碼塊中(花括號),所在它在另一個作用域中。這稱爲塊作用域或結構作用域。
棧指針表示棧中下一個空閒存儲單元的地址。棧是由上向下填充。當數據進棧後,棧指針就會隨之進行調整。
例如:
int a = 10;
double b = 30.0;
int a = 10;之後爲a分配4個字節,即79999~ 79996;double佔8個字節,即79995~ 79988;
2. 引用數據類型
堆,全稱託管堆。是處理器可用內存的另一個內存區域。由下向上分配
假設有類Customer
Customer a;
a = new Customer();
代碼執行到Customer a;在棧上給Customer分配4個自接的空間,但這只是一個引用。執行到a = new Customer();時,會在堆中給a分配一個適當大小的空間已存儲Customer對象,並將棧中的a的值設置爲分配給Customer對象的內存地址。
3.垃圾回收
堆中不會出現這種情況,因爲垃圾回收器會將其他對象移動到堆的端部,再次形成一個連續的內存塊。這就是託管和非託管的堆的區別。使用託管堆就只需要讀取堆指針的值計科,不需要遍歷鏈表來查找一個地方放置數據。
注意:可以調用System.GC.Collect()強迫垃圾回收器在某個地方運行。
創建對象時,會將對象放在託管堆上。堆的第一部分稱爲第0代。創建新對象時,會將他移動到這個部分。因此這裏駐留了最新的對象。
垃圾回收過程中,遺留的舊對象會進行壓縮放在第1代對應的部分上。再次進行垃圾回收時,老對象的這種移動會再次發生,這意味着第一代變爲第二代,第0代對象移動到第1代,第0帶仍用來保存新對象。
注意:在給對象分配空間時,如果超過了第0代對應的部分的容量,就會進行垃圾回收。
這個過程會極大的提高應用程序的性能。一般而言,新創建的對象都是可以進行回收的,而且可能回收大量比較新的對象。如果這些對象是相鄰的也會使應用程序執行速度變快。
在.NET中,垃圾回收提高性能的另一領域是架構處理堆上較大對象的方式。在.NET中,大於85000個字節的對象有自己的託管堆,而不是在主堆上,稱爲大託管堆。
在進一步改進垃圾回收後,應用程序線程只會對第一代和第0代對象實施回收,而其他代放在後臺執行,縮短時間。
有助於垃圾回收的另一項優化是垃圾回收的平衡,它用於服務器的垃圾回收。對於服務器,每一個邏輯服務器都有一個垃圾回收堆。因此一個垃圾回收堆用盡了內存時,會調用垃圾回收程序,所有其他堆也會觸發垃圾回收程序,這就造成浪費。垃圾回收會平衡這些堆——小對象堆和大對象堆。推進這個平衡過程,可以減少不必要的回收。
三、強引用和弱引用
垃圾回收器不能回收仍在使用的對象——強引用。可以回收不在根表直接或間接引用的託管內存,但是可能會忘記釋放引用。
注意:如果對象相互引用——A引用B,B引用C,C引用A,則GC可能會銷燬所有對象。
計入有一個對象MyClass,並創建一個變量myclassVariable來引用它。那麼在這個變量的作用域內,就存在對MyClass的強引用
var myclassVariable = new MyClass();
這意味着垃圾回收器不能回收MyClass對象使用的內存。可以穿件一個緩存對象,它引用myclassVariable對象。
var myCache = new MycaChe();
myCache.Add(myclassVariable);
用完myclassVariable,可以將他指定爲NULL
myclassVariable = null;
現在如果運行垃圾回收器,就不能釋放myclassVariable引用的內存,因爲在緩存對象中使用。但這樣的引用很容易被忘記,使用WeakRaference避免。
注意:使用事件很容易錯過引用的清理。此時也可以使用弱引用
弱引用是使用WeakReference類創建的。使用構造函數,可以傳遞強引用。
var myWeakReference = new WeakReference(new dataobject);
if (myWeakReference.IsAlive)
{
dataobject strongReference = myWeakReference.Target as dataobject;
if (strongReference != null)
{
//use the strongReference
}
else
{
//reference not available
}
}
【弱引用這塊沒怎麼看懂,以後重新編寫此處,會的小夥伴可以教我一下嗎】
四、處理非託管的資源
C#編程中,不需要擔心不再需要的對象,垃圾回收器會完成這項任務。但是垃圾回收器不知帶如何處理非託管資源,例如:文件句柄、網絡連接和數據庫連接。託管類在封裝對非託管資源的引用時要制定專門的規則,確保回收實例時釋放。
兩種機制來解決這一問題:
- 聲明析構函數
- 實現System.IDisposable接口
1.析構函數或終結器
C#在執行析構函數時,它會隱式的將析構函數古代碼轉換爲等駕馭重寫Finallize()方法的代碼,從而確保執行父類的Finallize()方法;
C#使用析構函數比C++次數少的原因:
- C#析構函數不穩定
- 延遲對象從內存銷燬的時間
- 對性能的影響顯著
2.IDisposable接口
C#用IDisposable接口代替析構函數,不帶參數,返回void。
假設有有一個A類需要使用外部資源,使用它並銷燬他:
var a = new A();
//do somesthing
a.Dispose();
如果怕出現異常:
A a = null;
try
{
a = new A();
//do somesthing
}
finally
{
a?.Dispose();
}
3.using語句
使用異常處理,即使異常也能確保調用了Dispose()方法,但重複這樣的結構,代碼容易混淆。可以使用using語句,當實現IDisposable接口的語句的對象引用超出作用域時就會自動調用Dispose()方法。
using (var a = new A())
{
//do somesthing
}
4.實現IDisposable接口和析構函數
舉一個實現IDisposable接口和析構函數的例子:
public class UserPreferences:IDisposable
{
private bool _idDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_idDisposed)
{
if (disposing)
{
///
}
///
}
_idDisposed = true;
}
~UserPreferences()
{
Dispose(false);
}
public void SomeMethod()
{
if (_idDisposed)
{
throw new ObjectDisposedException("ResourceHolder");
}
}
}
上述例子中Dispose有第二個重構方法,這是真正完成清理工作的方法,由析構函數調用。
GC.SuppressFinalize(this);表示該對象已經清理,不需要再次清理。
5.IDisposable和終結器的規則
- 如果類定義實現了IDisposable的成員,也應該實現IDisposable;
- 實現IDisposable並不意味着實現終結器。要是香釋放本機資源,就需要終結器。
- 如果實現了終結器,也應該實現IDisposable接口。
- 在終結器的實現代碼中,不能訪問已終結的對象