对于.net的程序,大多数情况下的内存泄露都和对象绑定在事件上的没有被反注销有关,也有一部分是COM 没有解除引用,当然还有一些其它的状况。
当我们确定程序存在managed的内存泄露的时候,我们怎么去进一步确定泄露到底是哪些对象引起的呢?
第一步,我们让系统起起来,然后跑一些scenario来warm up,然后抓一个dump(有些情况下也可以在系统起起来之后马上抓dump)。对于64位机器可以直接在进程管理器里面选中进程右键create dump,对于32位的机器,可以用System Internals的Process Explorer(http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx)来抓取。
第二步,重复跑有内存泄露的scenario一定次数,比如说10,或者100次,根据你的需要定,然后再抓一个dump。
第三步,用windbg打开第一次抓的dump文件,在正确加载了symbol路径和SOS以后,运行命令”!dumpheap –stat”,这个命令将输出heap上的所有对象。将输出拷出来保存到文本文件,比如说1.txt。对第二次抓的dump做同样的操作,将输出文件保存到2.txt。
第四步,用比较工具比较1.txt和2.txt,看看哪些对象增长比较明显,特别是对象的数量是第二步中跑scenario的倍数。比如说第二步跑了100次,而堆上对象的数量就加了100或者说是几百个,那么很有可能这个对象存在泄露。至于比较工具我一般用WinMerge(http://winmerge.org/)
第五步,查看代码,看看疑似泄露的对象是在哪里创建的,是否确实没有被销毁,然后作出相应的修改。
大多数情况下,上面的五个步骤能解决掉问题。
但是有些情况下,比如说对象可能在很多地方被创建,而且挂在remoting事件上,这个时候就不太容易找到来源,对于这种情况我们一般在对象里面加一个字段用来记录这个对象创建时的call stack,然后在构造函数里面初始化这个字段。
class Program { public string StackTrace;
public Program() { StackTrace = Environment.StackTrace; } } |
然后我们再重新测试,然后抓dump分析,下面是示例
1.根据前面”!dumpheap –stat”的比较结果,找到那些没有被释放的对象。可以根据对象的类型来找,也可以用前面的对象的method table来找。
0:115> !dumpheap -type My.CAddOnEventHelper (或者用 !dumpheap -mt <method table addr> in dumpheap -stat) Address MT Size 0000000010951af8 000007ff01b6d360 104 0000000011593da0 000007ff01b6d360 104 00000000117c3c10 000007ff01b6d360 104 00000000120196f0 000007ff023edb60 104 0000000012023720 000007ff023edb60 104 0000000012989c48 000007ff023edb60 104 0000000014d49de8 000007ff023edb60 104 total 0 objects Statistics: MT Count TotalSize Class Name 000007ff01b6d360 3 312 My.CAddOnEventHelper 000007ff023edb60 4 416 My.CAddOnEventHelper |
2.在上面的对象中随便找一个,然后dump出来
0:115> !do 0000000010951af8 Name: My.CAddOnEventHelper MethodTable: 000007ff01b6d360 EEClass: 000007ff01cd3638 Size: 104(0x68) bytes File: C:\bin\My.Business.dll Fields: MT Field Offset Type VT Attr Value Name 000007fee8d77248 4000d74 8 ...Channels.IChannel 0 instance 000000001524a778 channel 000007fee8d2d440 4000d76 30 System.Boolean 1 instance 1 connected 000007ff014b0e58 4000d79 10 System.Timers.Timer 0 instance 0000000010958930 timer 000007fee8d2d440 4000d7a 31 System.Boolean 1 instance 0 isDisposed 000007fee8d2d5a8 4000d7b 38 ...olean, mscorlib]] 1 instance 0000000010951b30 becIsDisabled 000007fee8d2d440 4000d7c 32 System.Boolean 1 instance 1 useGenuineChannels 000007fee8d2d440 4000d7d 33 System.Boolean 1 instance 0 inTimer 000007fee8d25880 4000d7e 18 System.Object 0 instance 000000001524ced8 remoteObject 000007fee8d26728 4000d7f 20 System.String 0 instance 0000000010351420 serverMachineName 000007ff01330ca8 4000d80 28 ...ConnectionManager 0 instance 0000000010951c68 connectionManager 000007fee8d25880 4000d75 a20 System.Object 0 shared static connectionLock >> Domain:Value 0000000000129940:NotInit 0000000005f81fc0:00000000109407a8 0000000007477df0:0000000011a183c0 00000000074c9130:00000000111b8738 00000000074c9d90:0000000010958bb8 00000000075782e0:00000000113ab768 0000000007ba9640:0000000011b309d8 000000002afa00f0:NotInit << 000007fee8d2c610 4000d77 7d0 System.Int32 1 shared static connectionCount >> Domain:Value 0000000000129940:NotInit 0000000005f81fc0:2 0000000007477df0:2 00000000074c9130:0 00000000074c9d90:4 00000000075782e0:5 0000000007ba9640:0 000000002afa00f0:NotInit << 000007fee8d49bb8 4000d78 a28 ...tions.IDictionary 0 shared static remotingConfigEntries >> Domain:Value 0000000000129940:NotInit 0000000005f81fc0:0000000010940e10 0000000007477df0:0000000011a18b70 00000000074c9130:00000000111c29e8 00000000074c9d90:00000000109715f0 00000000075782e0:00000000113af458 0000000007ba9640:0000000011b31040 000000002afa00f0:NotInit << 000007fee8d2d440 40005d7 3c System.Boolean 1 instance 0 initSynchronously 000007fee8d25880 40005d8 40 System.Object 0 instance 0000000010951c50 channelRegistrationLock 000007fee8d2d440 40005da 3d System.Boolean 1 instance 0 isDisposed 000007fee8d2d440 40005dc 3e System.Boolean 1 instance 0 staleData 000007fee8d26728 40005dd 48 System.String 0 instance 0000000010972130 StackTrace 000007ff01b6d4f8 40005de 50 ...angedEventHandler 0 instance 0000000000000000 optionChangedDelegate 000007ff01b6d4f8 40005df 58 ...angedEventHandler 0 instance 0000000000000000 LocalOptionChangedEvent 000007fee8d25880 40005d9 5f0 System.Object 0 shared static gcRegistrationLock >> Domain:Value 0000000000129940:NotInit 0000000005f81fc0:NotInit 0000000007477df0:NotInit 00000000074c9130:NotInit 00000000074c9d90:0000000015262198 00000000075782e0:NotInit 0000000007ba9640:NotInit 000000002afa00f0:NotInit << 000007ff013303c8 40005db 5f8 ...ctionEventHandler 0 shared static LocalRemoteConnectionEvent >> Domain:Value 0000000000129940:NotInit 0000000005f81fc0:NotInit 0000000007477df0:NotInit 00000000074c9130:NotInit 00000000074c9d90:0000000000000000 00000000075782e0:NotInit 0000000007ba9640:NotInit 000000002afa00f0:NotInit << |
3.找到前面添加的StackTrace字段,然后dump出来
0:115> !dumpobj 0000000010972130 Name: System.String MethodTable: 000007fee8d26728 EEClass: 000007fee88aed68 Size: 4726(0x1276) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll String: at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) at System.Environment.get_StackTrace() at My.CAddOnEventHelper..ctor() in D:\home\Business\Workflow\CCAddOnEventHelper.cs:line 110 at My.CAddOnAgent..ctor() in D:\home\Business\Workflow\CCAddOnAgent.cs:line 64 at My.CImageStatusMonitor.CCImageStatusMonitor.InternalInit(String uniqueId, ICCApplicationCoordinator masterController) in D:\home\Services\CCImageStatusMonitor\CCImageStatusMonitor.cs:line 609 at My.CServiceBaseCore.Init(String uniqueId, ICCApplicationCoordinator masterController) in D:\home\Services\Common.Core\CCServiceBaseCore.cs:line 359 at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs) at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext) at System.Runtime.Remoting.Messaging.ServerObjectTerminatorSink.SyncProcessMessage(IMessage reqMsg) at System.Runtime.Remoting.Messaging.ServerContextTerminatorSink.SyncProcessMessage(IMessage reqMsg) at System.Runtime.Remoting.Channels.CrossContextChannel.SyncProcessMessageCallback(Object[] args) at System.Runtime.Remoting.Channels.ChannelServices.DispatchMessage(IServerChannelSinkStack sinkStack, IMessage msg, IMessage& replyMsg) at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream) at System.Runtime.Remoting.Channels.Ipc.IpcServerTransportSink.ServiceRequest(Object state) at System.Runtime.Remoting.Channels.SocketHandler.ProcessRequestNow() at System.Runtime.Remoting.Channels.SocketHandler.BeginReadMessageCallback(IAsyncResult ar) at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP) Fields: MT Field Offset Type VT Attr Value Name 000007fee8d2c610 4000103 8 System.Int32 1 instance 2350 m_stringLength 000007fee8d2b150 4000104 c System.Char 1 instance 20 m_firstChar 000007fee8d26728 4000105 10 System.String 0 shared static Empty >> Domain:Value 0000000000129940:0000000010351420 0000000005f81fc0:0000000010351420 0000000007477df0:0000000010351420 00000000074c9130:0000000010351420 00000000074c9d90:0000000010351420 00000000075782e0:0000000010351420 0000000007ba9640:0000000010351420 000000002afa00f0:0000000010351420 << |
这样,我们就知道了对象的来源,然后可以在对应的地方作修改。
这里给出了一种用windbg解决简单的托管对象内存泄露的方法。现在市场上有很多工具可以非常直观地找到泄露点,比如ANTS memory profiler(http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/ )和.Net memory profiler(http://memprofiler.com/) 等,可以供大家使用。