在.NET編程中,得益於有效的內存管理機制,對象的建立和使用比較方便,大多數狀況下咱們無須關心對象建立和分配內存的細節,也能夠放心的把對象的清理交給自動垃圾回收來完成。因爲.NET類庫對系統底層對象進行了封裝,咱們也不須要調用Windows API來操做非託管對象。但不直接操做非託管對象,並不意味着程序不會間接建立這些對象,若是不瞭解.NET對象與非託管資源的關係,咱們頗有可能由於不恰當的使用這些託管對象,而致使非託管資源泄露。本文嘗試說明Windows對象和句柄的基本概念,以及.NET編程中的對象與它們的關係,並結合一些簡單的示例程序來探討句柄泄露的話題。編程
1、什麼是句柄?安全
Windows編程中,程序須要訪問各類各樣的資源,如文件、網絡、窗口、圖標和線程等。不一樣類型的資源被系統封裝成不一樣的數據結構,當須要使用這些資源時,程序須要依據這些數據結構建立出不一樣的對象,當操做完畢並再也不須要這些對象時,程序應當及時釋放它們。在Windows中,應用程序不能直接在內存中操做這些對象,而是經過一系列公開的Windows API由對象管理器(Object Manager)來建立、訪問、跟蹤和銷燬這些對象。當調用這些API建立對象時,它們並不直接返回指向對象的指針,而是會返回一個32位或64位的整數值,這個在進程或系統範圍內惟一的整數值就是句柄(Handle)。隨後程序再次訪問對象,或者刪除對象,都將句柄做爲Windows API的參數來間接對這些對象進行操做。在這個過程當中,句柄做爲系統中對象的標識來使用。網絡
對象管理器是系統提供的用來統一管理全部Windows內部對象的系統組件。這裏所說的內部對象,不一樣於高級編程語言如C#中「對象」的概念,而是由Windows內核或各個組件實現和使用的對象。這些對象及其結構,要麼不對用戶代碼公開,要麼只能使用句柄由封裝好的Windows API進行操做。C#編程中,多數狀況下,咱們並不須要與這些Windows API打交道,這是由於.NET類庫對這些API又進行了封裝,但咱們的託管程序仍然會間接建立出不少Windows內部對象,並持有它們的句柄。數據結構
如上所說,句柄是一個32位或64位的整數值(取決於操做系統),因此在32位系統中,C#徹底能夠用int來表示一個句柄。但.NET提供了一個結構體System.IntPtr專門用來表明句柄或指針,在須要表示句柄,或者要在unsafe代碼中使用指針時,應當使用IntPtr類型。編程語言
2、C#中建立文件句柄的過程工具
舉例來講,文件屬於一種非託管的系統資源。在C#中,能夠用File類的靜態方法Open來獲得一個FileStream對象,來對磁盤文件進行讀寫操做。FileStream對象自己是託管對象,它是如何與文件這個非託管資源產生聯繫的呢?測試
大體說來,C#中打開文件的操做會通過下列步驟:操作系統
3、經過句柄操做對象的好處線程
Windows不容許應用程序直接訪問內存中更底層的對象,而是由對象管理器統一管理,總的來講,至少有如下好處:設計
4、查看進程的句柄數量
到如今爲止,本文討論的全是看不見的概念,有必要來直觀的看一下系統中的句柄使用狀況。有多種方式能夠查看進程的句柄使用狀況,先從兩個工具開始,Windows任務管理器和Process Explorer。
任務管理器默認不顯示句柄數,須要在「查看」-「選擇列」中勾選「句柄數」後,纔會顯示進程中當前打開的句柄數量。以下圖所示,能夠看到記事本進程當前打開59個句柄。
系統自帶的任務管理器查看句柄數量很方便,但若是想知道這些句柄具體是什麼,可使用Process Explorer。Process Explorer是Windows Sysinternals工具包中的一個進程查看器,能夠從這裏下載。若是你看到的視圖跟下圖不一樣,能夠點擊View,選中Show Lower Pane,並在Lower Pane View中選擇Handles。在列表中選擇進程後,下方面板中會顯示該進程中句柄的詳細列表。
5、爲何關注句柄數
句柄指向的是諸如窗口、線程、文件、菜單、進程和定時器之類的系統資源,和全部被稱爲「資源」的事物同樣,稀缺性是它們共同的特色。對於計算機和操做系統來說,內存是一種稀缺資源,而全部的句柄和對象都存儲在內存中。基於這個事實,操做系統不容許進程無限制的建立對象和句柄。對於任務管理器中的「句柄數」來說,每一進程容許打開的句柄數理論上來說可達2^24個,但因爲內存的限制,實際數字大打折扣。在個人測試中,32位的.NET進程「句柄數」在達到1500萬以上後,程序開始出現各類各樣的問題。事實上絕大多數程序不會使用到這麼多句柄,除非特殊須要,在軟件編程中,若是本身的程序「句柄數」上千甚至是幾千時,就須要引發特別注意,這通常說明程序中已經存在句柄泄露的狀況。
你可能已經留意到,本文前面任務管理器中,除了顯示進程的「句柄數」以外,還顯示了「用戶對象」和「GDI對象」的數量,它們屬於另外兩種句柄。具體的區別咱們將在後面介紹,如今咱們須要清楚的是,系統對於這兩種對象一樣設置了數量限制。對於「用戶對象」和「GDI對象」來講,每一個進程容許建立的數量上限是在註冊表中設定的,分別是HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows中的USERProcessHandleQuota項和GDIProcessHandleQuota項,在Windows 7的32位操做系統上,兩個項都被默認設置爲10000。你能夠更改這個設置,用戶對象最多隻能設定爲18000個,GDI對象最多爲65536個。可是改變這個設置是不被推薦的,通常狀況下當你的應用程序須要用到超過10000個用戶對象或GDI對象時,應該首先檢查哪裏出現了句柄泄露,而不是更改上限數量;另外一方面,更改上限並不意味着應用程序就真的能夠建立和使用這麼多對象句柄,實際可用的數量同時受制於當前系統可用內存。