GDI+的leak

來源:互聯網
上載者:User

GDI+自身是否有leak,我們不去管,現在說的是.NET代碼中的處理。
首先看我這個簡單的helper

using System;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;

public class MemoryReport{
    [DllImport("user32.dll", CharSet=CharSet.Auto)]
    public static extern long GetGuiResources(IntPtr hProcess, long flag);

    public static string Write(){
        Process p = Process.GetCurrentProcess();
        ing hcount = p.HandleCount;
        long psize = p.PrivateMemorySize64;
        long vsize = p.VirtualMemorySize64;
        long workset = p.WorkingSet64;
        long gcsize = GC.GetTotalMemory(false);
        int gdiobjs = (int)(GetGdiResources(p.Handle,0));
        int userobjs = (int)(GetGdiResources(p.Handle,1));

        return String.Format("Handle count:{0:N0},Private Bytes:{1:N0}K, Virtual Bytes:{2:N0}K, Working Set:{3:N0}K, GC Heap Size:{4:N0}K, GDI Objects:{5:N0}, User Objects:{6:N0}", hcount, psize>>10, vsize>>10, workset>>10, gcsize>>10, gdiobjs, userobjs);
}
        
    }

現在我們做一個winform程式,放一個button,在click裡面寫如下測試代碼:for(int i=0;i<1000;i++){
    Bitmap b = new Bitmap("c:\\1.bif");
    IntPtr ip = b.GetHbitmap();
    Bitmap b2 = Bitmap.FromHbitmap(ip);
}

MessageBox.Show(MemoryReport.Write());

觀察每次的結果,Private Bytes/ Virtual Bytes/ Working Set基本是一個上漲的走向。但是我們感興趣的是這幾個地方:
1、Handle count:這個值一般會波動變化,在這裡例子裡面,你把程式運行起來後,用taskmgr來觀察Handle Count一欄(預設的沒有,需要你自己手工添加這個column),一般是100以下。然後點一下按鈕,handle count會增長1000左右,再點幾次,會在1000上下波動,不會繼續增長。
2、GDI Objects:這個值每次會增加1000
3、你連續點10次這個button,嘣!程式crash了。。。如果看dump裡面的異常,會是什麼bitmap的一個構造方法的parameter不正確。
4、GC Heap Size很小很小,我這裡是2M。但是virtual size很大。

對於1,為什麼這樣,我不清楚;對於2,原因在於GetHbitmap返回的是一個Unmanged resource,GC不會回收(即使你使用了GC.Collect()這個值也不會下降的);對於3,OS預設的每個process的GDI objects上限為10000個,我們代碼中是迴圈了1000次,所以如果你點了10次button,程式就會完蛋。對於4,說明leak的資源是unmanged resource,so,gc heap看起來很乖。

那麼,如何修複上面的問題2?既然是unmanged resource,我們就要從unmanged找起。[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b = new Bitmap("c:\\1.bif");
    IntPtr ip = b.GetHbitmap();
    Bitmap b2 = Bitmap.FromHbitmap(ip);

     DeleteObject(ip);
}

MessageBox.Show(MemoryReport.Write());

嗯,再運行一次,好了!GDI objects穩定了,再也沒有變化過。
不過,我們修改一下迴圈計數器,到5000吧,然後觀察Handle count,波動的比較厲害,記憶體相關的三組數值也稍有變化。好,我們再修改一次程式[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b = new Bitmap("c:\\1.bif");
    IntPtr ip = b.GetHbitmap();
    Bitmap b2 = Bitmap.FromHbitmap(ip);

     b.Dispose();
     b2.Dispose();

     DeleteObject(ip);
}

MessageBox.Show(MemoryReport.Write());

重新run一次,嗯,這個世界終於清靜了,handle count/gdi resource/ mem size都很平穩。

so,總結一下,對於類似上面的、可能被反覆調用的type,如GDI+ obj,可以考慮使用完畢後立刻Dispose,這樣可以被GC提早回收。對於返回一個IntPtr的方法,要仔細看,是不是需要再call win32裡面對應的Delete方法。

對於絕大多數GDI+ obj,我們只需要DeleteObject即可,但是對於icon,我記著是另外一個函數,有興趣的可以在msdn上查一下。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.