• GDI+的leak


    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上查一下。
  • 相关阅读:
    信息安全系统设计基础学习总结第二周
    一维数组求和2
    求一维数组的最大子数组的和
    第三周学习进度
    构建之法阅读笔记二
    四则运算三
    学习进度第二周
    四则运算二
    测试第二次程序
    阅读计划
  • 原文地址:https://www.cnblogs.com/juqiang/p/954920.html
Copyright © 2020-2023  润新知