深刻理解C 中資源釋放

2022-05-04 16:00:11 字數 4119 閱讀 8320

今天我的乙個朋友看到我寫的那篇《c#中用ajax驗證使用者登入》時,給我指出了點小毛病。就是在使用者登入時,如果使用者登入失敗,在下面這段**中,都會new出來乙個user物件,如果連續登入失敗多次,就會生成多個user物件,而它們在登入失敗後已經無用了,依然佔據著記憶體,就算是c#有垃圾**機制,但不確定什麼時候對這些物件進行**。

然後去網上找了一篇c#資源釋放的文章,講的很透徹,和大家分享一下。

首先,我們需要明確2個概念。

第乙個就是很多人用.net寫程式,會談到託管這個概念。那麼.net所指的資源託管到底是什麼意思,是相對於所有資源,還是只限於某一方面資源?很多人對此不是很了解,其實.net所指的託管只是針對記憶體這乙個方面,並不是對於所有的資源;因此對於stream,資料庫的連線,gdi+的相關物件,還有com物件等等,這些資源並不是受到.net管理而統稱為非託管資源。而對於記憶體的釋放和**,系統提供了gc-garbage collector,而至於其他資源則需要手動進行釋放。

那麼第二個概念就是什麼是垃圾,通過我以前的文章,會了解到.net型別分為兩大類,乙個就是值型別,另乙個就是引用型別。前者是分配在棧上,並不需要gc**;後者是分配在堆上,因此它的記憶體釋放和**需要通過gc來完成。gc的全稱為「garbage collector」,顧名思義就是垃圾**器,那麼只有被稱為垃圾的物件才能被gc**。也就是說,乙個引用型別物件所占用的記憶體需要被gc**,需要先成為垃圾。那麼.net如何判定乙個引用型別物件是垃圾呢,.net的判斷很簡單,只要判定此物件或者其包含的子物件沒有任何引用是有效的,那麼系統就認為它是垃圾。

明確了這兩個基本概念,接下來說說gc的運作方式以及其的功能。記憶體的釋放和**需要伴隨著程式的執行,因此系統為gc安排了獨立的執行緒。那麼gc的工作大致是,查詢記憶體中物件是否成為垃圾,然後對垃圾進行釋放和**。那麼對於gc對於記憶體**採取了一定的優先演算法進行輪循**記憶體資源。其次,對於記憶體中的垃圾分為兩種,一種是需要呼叫物件的析構函式,另一種是不需要呼叫的。gc對於前者的**需要通過兩步完成,第一步是呼叫物件的析構函式,第二步是**記憶體,但是要注意這兩步不是在gc一次輪循完成,即需要兩次輪循;相對於後者,則只是**記憶體而已。

很明顯得知,對於某個具體的資源,無法確切知道,物件析構函式什麼時候被呼叫,以及gc什麼時候會去釋放和**它所占用的記憶體。那麼對於從c、c++之類語言轉換過來的程式設計師來說,這裡需要轉變觀念。

那麼對於程式資源來說,我們應該做些什麼,以及如何去做,才能使程式效率最高,同時占用資源能盡快的釋放。前面也說了,資源分為兩種,託管的記憶體資源,這是不需要我們操心的,系統已經為我們進行管理了;那麼對於非託管的資源,這裡再重申一下,就是stream,資料庫的連線,gdi+的相關物件,還有com物件等等這些資源,需要我們手動去釋放。

如何去釋放,應該把這些操作放到**比較好呢。.net提供了三種方法,也是最常見的三種,大致如下:

1. 析構函式;

2. 繼承idisposable介面,實現dispose方法;

3. 提供close方法。

經過前面的介紹,可以知道析構函式只能被gc來呼叫的,那麼無法確定它什麼時候被呼叫,因此用它作為資源的釋放並不是很合理,因為資源釋放不及時;但是為了防止資源洩漏,畢竟它會被gc呼叫,因此析構函式可以作為乙個補救方法。而close與dispose這兩種方法的區別在於,呼叫完了物件的close方法後,此物件有可能被重新進行使用;而dispose方法來說,此物件所占有的資源需要被標記為無用了,也就是此物件被銷毀了,不能再被使用。例如,常見sqlconnection這個類,當呼叫完close方法後,可以通過open重新開啟資料庫連線,當徹底不用這個物件了就可以呼叫dispose方法來標記此物件無用,等待gc**。明白了這兩種方法的意思後,大家在往自己的類中新增的介面時候,不要歪曲了這兩者意思。

code

對於close來說不屬於真正意義上的釋放,除了注意它需要顯示被呼叫外,我在此對它不多說了。而對於析構函式而言,不是在物件離開作用域後立刻被執行,只有在關閉程序或者呼叫gc.collect方法的時候才被呼叫,參看如下的**執行結果。

code

執行的結果為:

after created!

destructor called!

顯然在出了create函式外,myclass物件的析構函式沒有被立刻呼叫,而是等顯示呼叫gc.collect才被呼叫。

對於dispose來說,也需要顯示的呼叫,但是對於繼承了idisposable的型別物件可以使用using這個關鍵字,這樣物件的dispose方法在出了using範圍後會被自動呼叫。例如:

using( disposeclass myclass = new disposeclass() )

如上執行的結果如下:

dispose called!

那麼對於如上disposeclass型別的dispose實現來說,事實上gc還需要呼叫物件的析構函式,按照前面的gc流程來說,gc對於需要呼叫析構函式的物件來說,至少經過兩個步驟,即首先呼叫物件的析構函式,其次**記憶體。也就是說,按照上面所寫的dispose函式,雖說被執行了,但是gc還是需要執行析構函式,那麼乙個完整的dispose函式,應該通過呼叫gc.suppressfinalize(this )來告訴gc,讓它不用再呼叫物件的析構函式中。那麼改寫後的disposeclass如下:

code

通過如下的**進行測試。

code

執行的結果如下:

dispose called!

after run!

顯然物件的析構函式沒有被呼叫。通過如上的實驗以及文字說明,大家會得到如下的乙個對比**。

析構函式

dispose方法

close方法

意義

銷毀物件

銷毀物件

關閉物件資源

呼叫方式

不能被顯示呼叫,會被gc呼叫

需要顯示呼叫

或者通過using語句

需要顯示呼叫

呼叫時機

不確定

確定,在顯示呼叫或者離開using程式塊

確定,在顯示呼叫時

那麼在定義乙個型別的時候,是否一定要給出這三個函式地實現呢。

我的建議大致如下。

1.提供析構函式,避免資源未被釋放,主要是指非記憶體資源;

2.對於dispose和close方法來說,需要看所定義的型別所使用的資源(參看前面所說),而決定是否去定義這兩個函式;

3.在實現dispose方法的時候,一定要加上「gc.suppressfinalize( this )」語句,避免再讓gc呼叫物件的析構函式。

c#程式所使用的記憶體是受託管的,但不意味著濫用,好地程式設計習慣有利於提高**的質量以及程式的執行效率。

出處:

深刻理解IdentityHashMap

新建pojo package test public class cat public string getname public void setname string name public integer getage public void setage integer age public...

深刻理解IdentityHashMap

新建pojo package test public class cat public string getname public void setname string name public integer getage public void setage integer age public...

JS深刻理解補充

對於函式的理解,首先看乙個函式定義 function functiondefined 顯而易見,functiondefined 為函式名字,在js中為指向這個函式體的指標,代表這個函式的指標的變數,並且和原始資料型別一樣儲存在棧中。而functiondefined函式體則儲存在堆中。每當new出乙個...