Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 843|回复: 0

WPF中解决内存泄露的几点提示与解决方法(转)

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-7-8 03:37:25 | 显示全部楼层 |阅读模式

    转自:http://www.cnblogs.com/LastPropose/archive/2011/08/01/2124359.html

    一直以来用WPF做一个项目,但是开发中途发现内存开销太大,用ANTS Memory Profiler分析时,发现在来回点几次载入页面的操作中,使得非托管内存部分开销从起始的43.59M一直到150M,而托管部分的开销也一直持高不下,即每次申请的内存在结束后不能完全释放。在网上找了不少资料,甚受益,现在修改后,再也不会出现这种现象了(或者说,即使有也不吓人),写下几个小心得:

    1. 慎用WPF样式模板合并

      我发现不采用合并时,非托管内存占用率较小,只是代码的理解能力较差了,不过我们还有文档大纲可以维护。

    2. WPF样式模板请共享

      共享的方式最简单不过的就是建立一个类库项目,把样式、图片、笔刷什么的,都扔进去,样式引用最好使用StaticResource,开销最小,但这样就导致了一些写作时的麻烦,即未定义样式,就不能引用样式,哪怕定义在后,引用在前都不行。

    3. 慎用隐式类型var的弱引用

      这个本来应该感觉没什么问题的,可是不明的是,在实践中,发现大量采用var与老老实实的使用类型声明的弱引用对比,总是产生一些不能正确回收的WeakRefrense(这点有待探讨,因为开销不是很大,可能存在一些手工编程的问题)

    4. 写一个接口约束一下

      谁申请谁释放,基本上这点能保证的话,内存基本上就能释放干净了。我是这么做的:

    复制代码
        interface IUIElement : IDisposable
    {
    /// <summary>
    /// 注册事件
    /// </summary>
    void EventsRegistion();

    /// <summary>
    /// 解除事件注册
    /// </summary>
    void EventDeregistration();
    }
    复制代码

    在实现上可以这样:

    复制代码
     1 #region IUIElement 成员
    2 public void EventsRegistion()
    3 {
    4 this.traineeReport.SelectionChanged += new SelectionChangedEventHandler(traineeReport_SelectionChanged);
    5 }
    6
    7 public void EventDeregistration()
    8 {
    9 this.traineeReport.SelectionChanged -= new SelectionChangedEventHandler(traineeReport_SelectionChanged);
    10 }
    11
    12 private bool disposed;
    13
    14 ~TraineePaymentMgr()
    15 {
    16 ConsoleEx.Log("{0}被销毁", this);
    17 Dispose(false);
    18 }
    19
    20 public void Dispose()
    21 {
    22 ConsoleEx.Log("{0}被手动销毁", this);
    23 Dispose(true);
    24 GC.SuppressFinalize(this);
    25 }
    26
    27 protected void Dispose(bool disposing)
    28 {
    29 ConsoleEx.Log("{0}被自动销毁", this);
    30 if(!disposed)
    31 {
    32 if(disposing)
    33 {
    34 //托管资源释放
    35 ((IDisposable)traineeReport).Dispose();
    36 ((IDisposable)traineePayment).Dispose();
    37 }
    38 //非托管资源释放
    39 }
    40 disposed = true;
    41 }
    42 #endregion
    复制代码

     比如写一个UserControl或是一个Page时,可以参考以上代码,实现这样接口,有利于资源释放。

    5. 定时回收垃圾

    复制代码
    DispatcherTimer GCTimer = new DispatcherTimer();
    public MainWindow()
    {
    InitializeComponent();
    this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾释放定时器 我定为每十分钟释放一次,大家可根据需要修改
      this.GCTimer.start();

    this.EventsRegistion(); // 注册事件
    }

    public void EventsRegistion()
    {
    this.GCTimer.Tick += new EventHandler(OnGarbageCollection);
    }

    public void EventDeregistration()
    {
    this.GCTimer.Tick -= new EventHandler(OnGarbageCollection);
    }

    void OnGarbageCollection(object sender, EventArgs e)
    {
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    }
    复制代码

    6. 较简单或可循环平铺的图片用GeometryDrawing实现

    一个图片跟几行代码相比,哪个开销更少肯定不用多说了,而且这几行代码还可以BaseOn进行重用。

    复制代码
    <DrawingGroup x:Key="Diagonal_50px">
    <DrawingGroup.Children>
    <GeometryDrawing Brush="#FF2A2A2A" Geometry="F1 M 0,0L 50,0L 50,50L 0,50 Z"/>
    <GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,0L 0,50L 0,25L 25,0L 50,0 Z"/>
    <GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,25L 50,50L 25,50L 50,25 Z"/>
    </DrawingGroup.Children>
    </DrawingGroup>
    复制代码

    这边是重用

    <DrawingBrush x:Key="FrameListMenuArea_Brush" Stretch="Fill" TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute"
    Drawing="{StaticResource Diagonal_50px}"/>

    上面几行代码相当于这个:

    7. 使用Blend做样式的时候,一定要检查完成的代码

    众所周知,Blend定义样式时,产生的垃圾代码还是比较多的,如果使用Blend,一定要检查生成的代码。

     

    8. 静态方法返回诸如List<>等变量的,请使用out

    比如

    public static List<String> myMothod()

    {...}

     

    请改成

    public static myMothod(out List<String> result)

    {...}

     

    9. 打针对此问题的微软补丁

    3.5的应该都有了吧,这里附上NET4的内存泄露补丁地址,下载点这里 (QFE:  Hotfix request to implement hotfix KB981107 in .NET 4.0 )

    这是官方给的说明,看来在样式和数据绑定部分下了点工夫啊:

    1. 运行一个包含样式或模板,请参阅通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展应用程序资源的 WPF 应用程序。 创建使用这些样式或模板的多个控件。 但是,这些控件不使用引用的资源。 在这种情况的一些内存WeakReference对象和空间泄漏的控股数组后,垃圾回收释放该控件。
    2. 运行一个包含的控件的属性是数据绑定到的 WPF 应用程序DependencyObject对象。 该对象的生存期是超过控件的生存期。 许多控件时创建,一些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
    3. 运行使用树视图控件或控件派生于的 WPF 应用程序,选择器类。 将控件注册为控制中的键盘焦点的内部通知在KeyboardNavigation类。 该应用程序创建这些控件的很多。 例如对于您添加并删除这些控件。 在本例中为某些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。

    继续更新有关的三个8月补丁,详细的请百度:KB2487367  KB2539634  KB2539636,都是NET4的补丁,在发布程序的时候,把这些补丁全给客户安装了会好的多。

    10.  对string怎么使用的建议

    这个要解释话就长了,下面仅给个例子说明一下,具体的大家去找找MSDN

    复制代码
            string ConcatString(params string[] items)
    {
    string result = "";
    foreach (string item in items)
    {
    result += item;
    }
    return result;
    }

    string ConcatString2(params string[] items)
    {
    StringBuilder result = new StringBuilder();
    for(int i=0, count = items.Count(); i<count; i++)
    {
    result.Append(items);
    }
    return result.ToString();
    }
    复制代码

    建议在需要对string进行多次更改时(循环赋值、连接之类的),使用StringBuilder。我已经把工程里这种频繁且大量改动string的操作全部换成了StringBuilder了,用ANTS Memory Profiler分析效果显著,不仅提升了性能,而且垃圾也少了。

     

    11. 其它用上的技术暂时还没想到,再补充...

     

    如果严格按以上操作进行的话,可以得到一个满意的结果:

    运行了三十分钟,不断的切换功能,然后休息5分钟,回头一看,结果才17M左右内存开销,效果显著吧。

    然后对于调试信息的输出,我的做法是在窗体应用程序中附带一个控制台窗口,输出调试信息,给一个类,方便大家:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;

    namespace Trainee.UI.UIHelper
    {
    public struct COORD
    {
    public ushort X;
    public ushort Y;
    };

    public struct CONSOLE_FONT
    {
    public uint index;
    public COORD dim;
    };

    public static class ConsoleEx
    {
    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32", CharSet = CharSet.Auto)]
    internal static extern bool AllocConsole();

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32", CharSet = CharSet.Auto)]
    internal static extern bool SetConsoleFont(IntPtr consoleFont, uint index);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32", CharSet = CharSet.Auto)]
    internal static extern bool GetConsoleFontInfo(IntPtr hOutput, byte bMaximize, uint count, [In, Out] CONSOLE_FONT[] consoleFont);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32", CharSet = CharSet.Auto)]
    internal static extern uint GetNumberOfConsoleFonts();

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32", CharSet = CharSet.Auto)]
    internal static extern COORD GetConsoleFontSize(IntPtr HANDLE, uint DWORD);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32.dll ")]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern int GetConsoleTitle(String sb, int capacity);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("user32.dll", EntryPoint = "UpdateWindow")]
    internal static extern int UpdateWindow(IntPtr hwnd);

    [System.Security.SuppressUnmanagedCodeSecurity]
    [DllImport("user32.dll")]
    internal static extern IntPtr FindWindow(String sClassName, String sAppName);

    public static void OpenConsole()
    {
    var consoleTitle = "> Debug Console";
    AllocConsole();


    Console.BackgroundColor = ConsoleColor.Black;
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WindowWidth = 80;
    Console.CursorVisible = false;
    Console.Title = consoleTitle;
    Console.WriteLine("DEBUG CONSOLE WAIT OUTPUTING...{0} {1}\n", DateTime.Now.ToLongTimeString());

    try
    {
    //这里是改控制台字体大小的,可能会导致异常,在我这个项目中我懒得弄了,如果需要的的话把注释去掉就行了
    //IntPtr hwnd = FindWindow(null, consoleTitle);
    //IntPtr hOut = GetStdHandle(-11);

    //const uint MAX_FONTS = 40;
    //uint num_fonts = GetNumberOfConsoleFonts();
    //if (num_fonts > MAX_FONTS) num_fonts = MAX_FONTS;
    //CONSOLE_FONT[] fonts = new CONSOLE_FONT[MAX_FONTS];
    //GetConsoleFontInfo(hOut, 0, num_fonts, fonts);
    //for (var n = 7; n < num_fonts; ++n)
    //{
    // //fonts[n].dim = GetConsoleFontSize(hOut, fonts[n].index);
    // //if (fonts[n].dim.X == 106 && fonts[n].dim.Y == 33)
    // //{
    // SetConsoleFont(hOut, fonts[n].index);
    // UpdateWindow(hwnd);
    // return;
    // //}
    //}
    }
    catch
    {

    }
    }

    public static void Log(String format, params object[] args)
    {
    Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + format, args);
    }
    public static void Log(Object arg)
    {
    Console.WriteLine(arg);
    }
    }
    }
    复制代码

    在程序启动时,可以用ConsoleEx.OpenConsole()打开控制台,用ConsoleEx.Log(.....)或者干脆用Console.WriteLine进行输出就可以了。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 22:39 , Processed in 0.057448 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表