C#內存釋放(垃圾回收)


問題背景----

今天寫了個很小的程序,程序的功能僅僅是截圖,但是如果長時間開啟並截圖的時候,程序會變的很大,從剛開始的運行在任務管理器中只有十幾K大小,運行一段時間后在任務管理器中看到程序可以達到1G或2G甚至更大;最初想到的是所有的截圖都保存在內存中,沒有釋放造成的。去檢查代碼,發現程序中已經使用GC.Collect();但是為什么程序還是會一直增加呢?由於程序中邏輯判斷等比較多,不方便跟蹤及查找。所以我自己單獨寫了個測試程序,去看看調用GC.Collect();釋放的問題?

測試環境----

首先准備一個對象(由於程序中使用了一些靜態變量),所以准備的對象如下:

public class CountObject
    {
        public static int Count = 0;
        public CountObject()
        {
            Count++;
        }

        ~CountObject()
        {
            Count--;
        }
    }

程序很簡單,只有一個靜態的計數變量。下面在看看主程序:

 1 static void Main(string[] args)
 2         {
 3             CountObject obj;
 4             for (int i = 0; i < 5; i++)
 5             {
 6                 obj = new CountObject();
 7                 //obj = null; // 這一步,只是為了更清晰些驗證引用的對象是否釋放!
 8                 GC.Collect();
 9 
10             }
11             //GC.Collect();
12             //GC.WaitForPendingFinalizers();
13 
14             // Count不會是1,因為Finalizer不會馬上被觸發,要等到有一次回收操作(GC.Collect())后才會被觸發。 GC.Collect();GC.WaitForPendingFinalizers();
15             Console.WriteLine(CountObject.Count);
16             Console.ReadKey();
17         }

程序也比較簡單,我做了如下測試:

1)使用以上程序運行,發現15行會輸出5,說明我們調用了GC.Collect();但程序並沒有執行釋放,因為查GC的官方解釋,是不確定的某個時刻進行回收。

2)把循環每次增大5個。當循環增加到125的時候,多次執行后發現,我本機測試,在第15行的輸出是1或125,當增加到10000,每次都輸出1,說明符合官方解釋;

根據以上代碼測試知道,當循環5次的時候,GC並不會立即執行,所以當執行5次循環的時候第8行沒起作用。既然不起作用,我們把他注釋暫時不用,把11和12行開啟。

3)把地8行注釋,11,12行開啟,執行5次循環,發現15行輸出1,多次執行結果相同。

4)再把11行注釋,12行開啟,執行5次循環,發現15行輸出5,多次執行結果相同。

5)再把12行注釋,11行開啟,執行5次循環,發現15行輸出5,多次執行結果相同。

根據4和5的才測試可以看到,當少量的循環時Finalizer不會馬上被觸發,要等到有一次回收操作(GC.Collect())執行后才會被觸發。所以我們可以顯式調用 GC.Collect();GC.WaitForPendingFinalizers();這兩行代碼進行強制回收的執行。

6)驗證,把第7行開啟,執行測試第15行為0,說明對象如果沒有任何的引用則可以強制回收。

以上是本人的一些測試,如果你還有更好的想法,可以提出一起討論;

版權歸個人所有,轉載請注明出處;

 


 

由內存釋放導致的問題:

軟件在測試力度加大情況下,可能導致的內存不足及崩潰的問題可能快速暴露,針對這些問題可以通過下面方式解決,歡迎補充。
1. 常用方式:
A)類文件中占用內存較大的全局變量,公共變量,類私有變量及類的實例用完之后手動設置為null或Dispose(),對局部變量不需要置null,但局部的實例需要Dispose或置null。
B)占用內存較大的變量或實例,在循環創建這些類或實例的地方適當進行置null或Dispose()后進行GC.Collect();
 
2. 結合代碼業務進行代碼重構:
A) 將主程序中的功能模塊化,如封裝到動態庫中后,通過訂閱的方式不再進行主動的業務請求,降低主進程負擔。
B) 對程序中會頻繁重復使用的類如心跳,網絡監控和彈出窗體,歷史信息類等,避免重復實例化,通過定義全局唯一靜態變量的方式即單例模式實現循環使用。
C) 優化代碼或重構
 
結論,通常合理使用方式1基本可以解決大部分內存不足導致的崩潰問題,但垃圾回收有時效性等底層判斷機制,主動垃圾回收對於內存快速消耗的情況可能效果不好(比如進行1秒百萬級,或者只需要幾千個並發,在置null和GC之前程序就已經死掉,即垃圾回收不能根本解決程序內存消耗和性能問題,需要不產生垃圾或少產生垃圾),如果對程序性能和質量有更好的要求,結合兩種方式使用。
 
舉例:

1.public void Dispose()
   {
        GC.Collect();
        GC.SuppressFinalize(this);
   }

2.線程終止及清理

          _thread.Abort();
          _thread.DisableComObjectEagerCleanup();
          _thread = null;

3.更徹底的垃圾回收

    /// <summary>
    ///設置線程工作的空間
    /// </summary>
    /// <param name="process">線程</param>
    /// <param name="minSize">最小空間</param>
    /// <param name="maxSize">最大空間</param>
    /// <returns></returns>
    [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
    public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
    /// <summary>      
    /// 釋放內存      
    /// </summary>      
    public static void ClearMemory()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        if (Environment.OSVersion.Platform == PlatformID.Win32NT)
        {
            SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
        }
    }

4.代碼重構太寬泛,自行總結

出處:https://blog.csdn.net/jiandanji123/article/details/79416398

==========================================================

C#如何立即回收內存

=

1.把對象賦值為null

2.立即調用GC.Collect();
 
注意:這個也只是強制垃圾回收器去回收,但具體什么時候執行不確定。 
 
代碼:
  public partial class Form1 : Form
    {
        [System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
        private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize);
 
 
        public Form1()
        {
            InitializeComponent();
        }
 
 
        private void button1_Click(object sender, EventArgs e)
        {
            ClearMemory();
        }
 
 
        #region 內存回收
   public  void ClearMemory()
 {
     GC.Collect();
     GC.SuppressFinalize(this);
 
 
     if (Environment.OSVersion.Platform == PlatformID.Win32NT)
     {
         SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
     }
 }
 #endregion
 
 
   private void timer1_Tick(object sender, EventArgs e)
   {
       string s="clearMemory";
       Form dt = new Form();
       dt.Text = s;
       //如果垃圾產生於timer中可能無法立刻回收資源,需加載timer tick事件中,立刻回收資源,或使用另外的timer控制回收時間。
       GC.Collect();
   }
 
}
[System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
        private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize);

 #region 內存回收
   public  void ClearMemory()
 {
     GC.Collect();
     GC.SuppressFinalize(this);
 
 
     if (Environment.OSVersion.Platform == PlatformID.Win32NT)
     {
         SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
     }
 }
 #endregion

ClearMemory();

 

=

出處:https://blog.csdn.net/xwnxwn/article/details/78009071

https://www.cnblogs.com/Leo_wl/p/11507878.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM