1. 程式人生 > 其它 >C# 記憶體洩漏

C# 記憶體洩漏

技術標籤:c#

概念

記憶體溢位:指程式在執行的過程中,程式對記憶體的需求超過了超過了計算機分配給程式的記憶體,從而造成“Out of memory”之類的錯誤,使程式不能正常執行。

記憶體洩露:記憶體洩漏指由於疏忽或錯誤造成程式不能釋放或不能及時釋放已經不再使用的記憶體的情況,是應用程式分配某段記憶體後,由於設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體不能回收和不能及時回收,最後可能導致程式執行緩慢或者崩潰的問題。

分析原因

1、你的物件仍被引用但實際上卻未被使用。 由於它們被引用,因此GC將不會收集它們,這樣它們將永久儲存並佔用記憶體。
2、當你以某種方式分配非託管記憶體(沒有垃圾回收)並且不釋放它們。

例項

1.使用event造成的記憶體洩漏

class TestClassHasEvent
    {
        public delegate void TestEventHandler(object sender, EventArgs e);
        public event TestEventHandler YourEvent;
        protected void _Event(EventArgs e)
        {
            YourEvent?.Invoke(this, e);
        }
    }
     class TestListener
{ private TestClassHasEvent test; public TestListener(TestClassHasEvent _test) { test = _test; test.YourEvent += new TestClassHasEvent.TestEventHandler(_Event); } void _Event(object sender, EventArgs e) { } public
TestListener() { SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);//靜態 } void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { } } class Program { static void DisplayMemory() { Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)); } static void Main() { DisplayMemory(); Console.WriteLine(); TestClassHasEvent hasevent = new TestClassHasEvent(); for (int i = 0; i < 10; i++) { Console.WriteLine("--- New Listener #{0} ---", i + 1); var listener = new TestListener(hasevent); //var listener = new TestListener(new TestClassHasEvent()); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); DisplayMemory(); } Console.Read(); } }

輸出結果
正常情況

上面的結果可以看出來,雖然進行了GC,但是實際上記憶體還是回慢慢的增大,這是因為hasevent例項一直存在,由於事件訂閱了TestListener內部的方法,因此TestListener例項也不會進行釋放。

 static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                var listener = new TestListener();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                DisplayMemory();

            }
            Console.Read();
        }

在這裡插入圖片描述
訂閱了靜態event的例項也不會進行釋放。

2、靜態變數

靜態變數中的成員所佔的記憶體不果不手動處理是不會釋放記憶體的,單態模式的物件也是靜態的,所以需要特別注意。因為靜態物件中的成員所佔的記憶體不會釋放,如果此成員是以個物件,同時此物件中的成員所佔的記憶體也不會釋放,以此類推,如果此物件很複雜,而且是靜態的就很容易造成記憶體洩露。

3、WPF繫結
WPF繫結實際上可能會導致記憶體洩漏。 一般我們都是繫結到DependencyObject或INotifyPropertyChanged物件。 下面的講的例子,WPF將建立從靜態變數到繫結源(即ViewModel)的強引用,從而導致記憶體洩漏。

這個View Model將永遠留在記憶體中:

public class MyViewModel
{
    public string _someText = "memory leak";
    public string SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
        }
    }
}

而這個View Model不會導致記憶體洩漏:

public class MyViewModel : INotifyPropertyChanged
{
    public string _someText = "not a memory leak";
 
    public string SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof (SomeText)));
        }
    }

該類是從INotifyPropertyChanged派生的,這樣會告訴wpf建立是的一個弱引用。

4、執行緒/計時器管理不當
建立一個具有對物件引用的執行緒,在不用的時候沒有停止執行緒/計時器,那麼這也會導致記憶體洩漏。

5、非託管資源

因為非託管資源所佔的記憶體不能自動回收,所以使用後必須手動回收,否則程式執行多次很容易造成記憶體洩露

public class SomeClass : IDisposable
{
    private IntPtr _buffer;
 
    public SomeClass()
    {
        _buffer = Marshal.AllocHGlobal(1000);
    }
 
 	//及時釋放
    public void Dispose()
    {
        Marshal.FreeHGlobal(_buffer);
    }
}

6、Dispose方法沒被呼叫,或Dispose方法沒有處理物件的釋放。這樣也會造成記憶體洩露
使用using可以避免產生這種問題

 using (MemoryStream stream = new MemoryStream()) 
{
    // ... 
}

在類中編寫一個Dispose()方法統一釋放所有物件,在解構函式中呼叫Disopose()。

   ~MyClass()
    {
        Dispose();
    }