最近,做了個服務程式,電信項目,需要保證7 ×18小時運行(0點到6點除開),採用事件驅動,結果,程式運行15天,記憶體佔用1G,為瞭解決記憶體泄露的問題,因此,有了這篇文章。。。。。
記憶體泄露,原因很多,因此,不同的情況有不同的解決辦法。
首先:說說本項目可能存在的記憶體泄露的原因。
1:多線程,資源變數的讀取,死迴圈(本程式不存在死迴圈。。)
2:資源沒有釋放完全,當然,本程式是由事件驅動,呵呵,原因有可能是由於事件沒有正確釋放造成的。。。
對於第2種:事件的沒正確釋放,是指什麼呢?
比如說:如下的代碼
Code
public class EventTest1:IDisposable
{
private int[] testArr = new int[100000000];
public event TestDelegate TestDelegate = null;
protected void OnTest()
{
if (TestDelegate != null)
{
TestDelegate();
}
}
public void Add()
{
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
OnTest();
}
void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
}
IDisposable 成員#region IDisposable 成員
public void Dispose()
{
SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
}
#endregion
}
除非您顯示代用Dispose這個方法,不然,絕對會出現記憶體泄露。。。
因此,為瞭解決記憶體泄露,考慮了3種辦法:
1:再編寫一個服務程式,每天定時關閉和重啟記憶體泄露的服務程式。
可行性描述:
(1):這樣做,最簡單,最有可能達到效果的辦法。為什麼說是最有可能達到效果呢?原因在於,如果用服務程式來重新啟動或者關閉一個服務程式的話,有可能關閉和啟動的之間得時間過短,後面啟動的服務程式,顯示出的記憶體佔用,還是那麼多。。。。到底是什麼原因造成這樣的情況,還在研究中。。。
(2):雖然這樣最簡單,但是,是最沒辦法的辦法,不推薦使用,況且,就算解決問題後,自己沒能夠獲得多少經驗和能力提升。
(3):除非您的客戶要求您馬上解決記憶體泄露的問題,不然,請別採用這個方法
2:最正宗也最符合開發設計思想的辦法,重構代碼,及時釋放掉需要釋放的資源
可行性描述:
(1)時間問題:當出現記憶體泄露後,客戶當然是希望能夠最快時間得到開發組已經解決問題的確認,顯然,重構一個程式,把代碼全部在重新清理一遍,顯然不是一下就能夠完成的。
解決問題:
(1):在這,也就說說Dispose模式了(不知道到底有沒有這個模式,好象自己看的java與設計模式上沒看到過。。)。
Dispose模式,代碼簡單,但是,方法絕對的經典。
IDisposable
釋放資源 ,實現 IDisposable#region 釋放資源 ,實現 IDisposable
bool mIsDisposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!mIsDisposed)
{
if (disposing)
{
DisposeOverride(true);
}
DisposeOverride(false);
mIsDisposed = true;
}
}
protected virtual void DisposeOverride(bool p)
{
if (p)
{
// 釋放託管資源
}
else
{
// 釋放非託管資源
}
}
#endregion
解構函式:
this.Dispose(false);
當然,如果您程式裡面所有的該釋放的資源都釋放了,還有記憶體泄露,那。。。我也沒辦法。。。。
3:使用應用程式定義域
引用官方的話,給點曙光:
使用應用程式定義域隔離可能終止進程的任務。如果正在執行任務的 AppDomain 的狀態變得不穩定,則可以卸載 AppDomain,但不會影響進程。當進程必須不重新啟動而長時間運行時,這一點很重要。還可使用應用程式定義域隔離不應共用資料的任務。
如果程式集被載入到預設應用程式定義域中,則當進程運行時將無法從記憶體中卸載該程式集。但是,如果開啟另一個應用程式定義域來載入和執行程式集,則卸載該應用程式定義域時也會同時卸載程式集。使用此技術最小化長時間啟動並執行進程的工作集,這些進程偶爾會使用大型 DLL。
最後一句話,沒看懂,但是,只要第一句,就足夠了。。
本人也是第一次使用AppDomain這東西,因此,只能給出代碼了:
1:首先,把所有的業務代碼等,都用一個類封裝起來。
2:開啟一個新的AppDomain。
AppDomain
Type testType = Type.GetType("BoCo.Hubei.HuangShi.AlarmLocation.Server.BtsAlarmLocationService");
Assembly ass = testType.Assembly;
string exeAssembly = ass.FullName;
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase =
AppDomain.CurrentDomain.BaseDirectory;
ads.DisallowBindingRedirects = false;
ads.DisallowCodeDownload = true;
ads.ConfigurationFile =
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
userCreateAppDomain = AppDomain.CreateDomain("BoCo.Hubei.HuangShi.AlarmLocation.Server.exe", null, ads);//嗎的,實在是沒明白,這樣建立的一個AppDomain後,在新的AppDomain中,用AppDomain.CurrentDomain.FriendlyName取得的名字,怎麼會被截去了後面4位,搞得我在這個名稱後,加了.exe
ObjectHandle objectHandle = userCreateAppDomain.CreateInstance(exeAssembly, "BoCo.Hubei.HuangShi.AlarmLocation.Server.BtsAlarmLocationService");
BtsAlarmLocationService btsAlarmLocationService = (BtsAlarmLocationService)objectHandle.Unwrap();
對於第1,3種方法,只是治標不治本,只是第3種比第1種,高深了那麼一點點。。。。
本文中,所有的代碼等,只提供了一個思路,程式到底怎麼操作,是您說了算。。。
當然,為了國慶能夠安心放假,本程式也就只有採用第3種方法,況且,沒使用過的方法,當然要用下。
最後,說下為什麼本程式會出現記憶體泄露而沒解決掉的原因:
1:客戶原因
本程式實現的功能,在局方來說,是第一次,因此,局方人員希望快速出結果,可以向領導彙報,來增加其業績。
2:本公司原因
(1)專案經理在明知道程式沒有測試完全的情況下,也催促開發組提供者上線運行測試,結果,上線後,程式的任何一個小小的修改,都需要經過局方領導簽字確認後,才能夠實施,況且,恰逢奧運,一個簽字流程走下來,時間花費太多。。。
(2)專案經理沒有認識到本系統對局方使用人員業績上帶來的影響,程式某天只要出現丁點問題,則局方當天的使用人員將暴跳如雷。。。
3:開發組原因
當然,這個是最重要的。
(1)開發組組長沒有強勢的態度去左右專案經理不上沒有測試通過的程式。
(2)開發組組員代碼編寫能力以及系統的全面考慮上,還存在一定問題。
廢話一堆,也只是先以簡單辦法解決當前問題。