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[i]);
}
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进行输出就可以了。

WPF WPF中解决内存泄露的几点提示与解决方法的更多相关文章

  1. 如何快速排查解决Android中的内存泄露问题

    概述 内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash.对于复杂度比较高.多人协同开发的项目来讲 ...

  2. 查找并修复Android中的内存泄露—OutOfMemoryError

    [编者按]本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验.有点完美主义者,喜爱美食. 本文系国内ITOM管理平台 One ...

  3. [Swift通天遁地]七、数据与安全-(11)如何检测应用程序中的内存泄露

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  4. 利用Instrument Leak来发现App中的内存泄露

    XCode提供了一组用于检测内存,调试动画,布局等的工具.对于调试一些性能问题,内存问题非常方便.这里我们使用Leak来发现代码中的内存泄露. 在Leak中启动我们的应用开始监控: 注意,在监控的时候 ...

  5. Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  6. LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  7. Java中的内存泄露 和 JVM GC(垃圾回收机制)

    一.什么是Java中的内存泄露? 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点, 首先,这些对象是可达的,即在有向图中,存在通路可以与其相连:其次,这些对象是无用的,即程序以 ...

  8. troubleshoot之:使用JFR解决内存泄露

    目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的 ...

  9. Visual studio 2017中 Javascript对于Xrm对象模型没有智能提示的解决办法

    Visual studio 2017中 Javascript对于Xrm对象模型没有智能提示的解决办法 先上个图.语法提示支持到 Microsoft Dynamics xRM API 8.2 也就是cr ...

随机推荐

  1. RedisTemplate连接不释放、Redis断线不重连问题、Redis连接数高飙升

    使用RedisTemplate操作Redis数据,但遇到网络断线后不会重新连接 毫无头绪 一顿捣鼓 最终解决 整理如下 帮助更多的人 1.起因 使用RedisTemplate 配置 开启了事务 ena ...

  2. Linux虚拟机配置SSH免密登录

    本环境为CentOS 7(点击镜像下载iso文件),无图界面. 启动SSH服务 在/usr/sbin/有一个文件为sshd,然后输入绝对路径/usr/sbin/sshd即可开启ssh服务. 然后输入命 ...

  3. 保存Total Commander的列宽

    Total Commander的默认列宽经常显示不全内容,需要手工调整,用"Menu -> Configuration -> Save Position"可以永久保存列 ...

  4. MySQL为什么不支持中文排序?

    前言 或许都知道,MySQL不支持中文排序,这样的说法可以说对也可以说也不对.接下来我们分析一下: 首先执行命令,查看编码集: SHOW VARIABLES LIKE 'character_set%' ...

  5. Java - Enum 枚举类型

    目录 前言 应用 定义 基本Enum特性 Enum的静态导入 Enum中添加新方法 Switch语句中的Enum Enum的继承 EnumSet的使用 EnumMap的使用 常量相关方法 枚举值向枚举 ...

  6. wpf 中的style

    我们通常说的模板是用来参照的,同样在WPF中,模板是用来作为制作控件的参照. 一.认识模板 1.1WPF菜鸟看模板 前面的记录有提过,控件主要是算法和数据的载体.控件的算法主要体现在可以激发的事件.可 ...

  7. Mysql 之 IFNULL(expr1,expr2) 对空不可判

    目标 当传入参数 @OrderId为空时 不做过滤 sql语句如下 SELECT o.* FROM `order` AS o LEFT JOIN receivemoneyconfirm AS re O ...

  8. uwp 的锁屏功能

    [UWP开发]自定义锁屏&桌面壁纸 mtobeiyf关注 2015.11.01 00:16:55字数 394阅读 1,249 调用通用的API来设置桌面壁纸,可以实现很多有趣的功能.在Wind ...

  9. java activity工作流

    java activity工作流 参考资料: 1.https://blog.csdn.net/jiangyu1013/article/details/73250902 2.https://blog.c ...

  10. 复习git

    git 常用点,详解 from my typora 文章目录 git 常用点,详解 git 模式解析 删除文件 方式一: 方式二: 远程库 配置忽略文件 查看版本库日志,以及版本回退 解决冲突 替换我 ...