内存溢出的几种情况
一:事件驱动
原因事件对象没有被释放,或者一直引用着
class TestClassHasEvent
{
public delegate void TestEventHandler(object sender, EventArgs e);
public event TestEventHandler YourEvent;
protected void OnYourEvent(EventArgs e)
{
if (YourEvent != null) YourEvent(this, e);
}
}
//public TestListener(TestClassHasEvent inject)
//{
// SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
//}
//void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
//{
//}
class TestListener
{
byte[] m_ExtraMemory = new byte[1000000];
private TestClassHasEvent _inject;
public TestListener(TestClassHasEvent inject)
{
_inject = inject;
_inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
}
void _inject_YourEvent(object sender, EventArgs e)
{
}
}
class Program
{
static void DisplayMemory()
{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
}
static void Main()
{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("--- New Listener #{0} ---", i + 1);
var listener = new TestListener(new TestClassHasEvent());
listener = null; //可有可无
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
DisplayMemory();
}
Console.Read();
}
}
解决方案
class TestListener : IDisposable
{
byte[] m_ExtraMemory = new byte[1000000];
private TestClassHasEvent _inject;
public TestListener(TestClassHasEvent inject)
{
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
}
void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
}
#region IDisposable Members
public void Dispose()
{
SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
}
#endregion
}
class Program
{
static void DisplayMemory()
{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
}
static void Main()
{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("--- New Listener #{0} ---", i + 1);
using (var listener = new TestListener(new TestClassHasEvent()))
{
//do something
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
DisplayMemory();
}
Console.Read();
}
}
上面两个例子一个内存泄露,一个没有内存泄露,我想你应该知道原因了,根本区别在于后者有个SystemEvents.DisplaySettingsChanged事件,这个事件是静态Static事件,所以绑定到这个事件上的对象都不会被释放
// Type: Microsoft.Win32.SystemEvents
// Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll
using System;
using System.ComponentModel;
namespace Microsoft.Win32
{
public sealed class SystemEvents
{
public static IntPtr CreateTimer(int interval);
public static void InvokeOnEventsThread(Delegate method);
public static void KillTimer(IntPtr timerId);
public static event EventHandler DisplaySettingsChanging;
public static event EventHandler DisplaySettingsChanged;
public static event EventHandler EventsThreadShutdown;
public static event EventHandler InstalledFontsChanged;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This event has been deprecated. http://go.microsoft.com/fwlink/?linkid=14202")]
[Browsable(false)]
public static event EventHandler LowMemory;
public static event EventHandler PaletteChanged;
public static event PowerModeChangedEventHandler PowerModeChanged;
public static event SessionEndedEventHandler SessionEnded;
public static event SessionEndingEventHandler SessionEnding;
public static event SessionSwitchEventHandler SessionSwitch;
public static event EventHandler TimeChanged;
public static event TimerElapsedEventHandler TimerElapsed;
public static event UserPreferenceChangedEventHandler UserPreferenceChanged;
public static event UserPreferenceChangingEventHandler UserPreferenceChanging;
}
}
注意Static,注意Singleton 这种static的东西生命周期很长,永远不会被GC回收,一旦被他给引用上了,那就不可能释放了。上面的例子就是SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);那就意味着这个类被SystemEvents.DisplaySettingsChanged 引用了,通过它的函数。另外一个要注意的是Singleton单例模式实现的类,他们也是static的生命周期很长,要注意引用链,你的类是否被它引用上,如果在它的引用链上,就内存泄露了。
另外还有注意程序运行期间不会释放的对象的事件
还有一种情况,既不是你的对象被static对象而不能释放,也不是Singleton,而是你的对象被一个永远不释放的对象引用着,这个对象或许不是static的。这种类型很多,比如你的界面有个MainForm,嘿嘿,这个MainForm永远不会关闭和释放的,被它引用了那就不会释放了。看个例子:
MainForm里面有个public event,MainForm里面打开Form2,然后关闭,看看Form2能不能释放:
public partial class MainForm : Form
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public MainForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form2 frm = new Form2();
this.PropertyChanged += frm.frm_PropertyChanged;
//MainForm referenced form2, because main form is not released, therefore form2 will not released.
DialogResult d = frm.ShowDialog();
GC.Collect();
ShowTotalMemory();
}
private void ShowTotalMemory()
{
this.listBox1.Items.Add(string.Format("Memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)));
}
}
Form2里面有个函数:
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
}
所以这种情况下,你的Event handler没有手动注销,那就肯定内存泄露了。
二:非托管资源
public partial class MemoryLeak
{
public static void Demo2()
{
while (true)
{
var x = File.OpenRead($"c:\\audio.log");
}
}
}
三:线程等待
public partial class MemoryLeak
{
/// <summary>
/// https://docs.microsoft.com/zh-cn/previous-versions/dotnet/articles/ms973858(v=msdn.10)?redirectedfrom=MSDN
/// </summary>
public static void Demo3()
{
{
while (true)
{
Console.ReadLine();
Thread t = new Thread(new ThreadStart(ThreadProc));
t.Start();
}
}
}
static void ThreadProc()
{
Thread.CurrentThread.Join();
}
}
四:字符串
本地程序集用const,跨程序集用readnoly
public partial class MemoryLeak
{
public static void Demo4()
{
List<string> asd = new List<string>();
while (true)
{
///String它存放的并不是普通的堆
///.NET有一个叫做内存驻地的东西---->字符串池--->字符串驻留池
///你的这些String会存放起来并不会"完全"遵照GC走
asd.Add(Guid.NewGuid().ToString());
if (asd.Count > 10000)
{
asd.Clear();
Console.WriteLine("asdasdasdasd" + 1);
Console.WriteLine("Error info");
Console.WriteLine("Error info");
Console.WriteLine("error");
Console.WriteLine("erorr");
}
}
}
}