在企业用户环境里,.NET Serviced Component使用广泛。它比较好的把传统COM+封装和.NET应用逻辑衔接了起来,在服务器端应用起到重要作用。.NET Serviced Component 的使用需要注意到很多方面,特别是要做到对象资源合理应用(pooling)和及时释放(Dispose)。现有文章就这方面有很多具体讨论。
在这里,我要分析的是一种以前处理过的特定情况下.NET Serviced Component的引发的High CPU的问题。不只一个客户遇到此类问题,觉得有必要和大家分享一下。
如果程序出现High CPU,这个应用程序线程一定出现了繁忙的运算,比如重复的计算或者长时间的循环,而不是等待数据库,锁,或其它资源。如果这个程序是.NET程序,频繁Garbage Collection导致High CPU就必须要重点考虑。因为每次GC都需要检查.NET 对象树和调整.NET堆, 是繁重的操作。我们可以通过捕捉性能日志(perfmon),查看.NET CLR Memory 对象下的%Time in GC 和 Induced GC 这两个性能计数器指标,来判断High CPU是不是和GC有关。
在这个例子里面,通过查看性能监视器,可以看到Induced GC增长非常的快速,表明是这次High CPU的直接原因:
通常情况下,Induced GC 出现是客户代码里面直接调用了GC.Collect 。然而,在查看客户代码后,发现根本没有GC.Collect出现。
我们注意到这个程序里面使用了.NET Enterprise Serviced Components, 开始便建议客户使用Dispose方法释放这些Components, 而不是用DisposeObject:
并且尝试启用对象池,, 发现改善并不明显:
http://www.codeproject.com/Articles/579/COM-Object-Pooling
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684273(v=vs.85).aspx
于是在这个程序High CPU的时候抓取了Memory Dump。
注:抓取Memory Dump的方法很多,可以用WinDBG, procdump或者DebugDiag
通过仔细分析Memory Dump,发现Induced GC逻辑出现在以下Call Stack里面:
非托管 Call Stack:
Child-SP RetAddr Call Site 00000000`0f59bbe8 00000000`7704ce00 ntdll!ZwWaitForSingleObject+0xa 00000000`0f59bbf0 000007fe`f52fa74a kernel32!WaitForSingleObjectEx+0x9c 00000000`0f59bcb0 000007fe`f52fa83b mscorwks!CLREventWaitHelper+0x42 00000000`0f59bd10 000007fe`f53cee38 mscorwks!CLREvent::WaitEx+0x63 00000000`0f59bdc0 000007fe`f53c1cb7 mscorwks!SVR::gc_heap::wait_for_gc_done+0x88 00000000`0f59be00 000007fe`f542913b mscorwks!SVR::GCHeap::GarbageCollectGeneration+0x147 00000000`0f59be90 000007fe`f5412ac3 mscorwks!SVR::GCHeap::GarbageCollect+0x5b 00000000`0f59bee0 000007fe`f57301a5 mscorwks!GCInterface::AddMemoryPressure+0x13b 00000000`0f59bf70 000007fe`f57b9beb mscorwks!RCW::AddMemoryPressure+0x15 00000000`0f59bfb0 000007fe`f57dbbf4 mscorwks!RCW::CreateRCW+0x17b 00000000`0f59c030 000007fe`f57dbdea mscorwks!COMInterfaceMarshaler::CreateObjectRef+0x74 00000000`0f59c130 000007fe`f58843f4 mscorwks!COMInterfaceMarshaler::WrapWithComObject+0x3a 00000000`0f59c1a0 000007fe`f0268995 mscorwks!MarshalNative::WrapIUnknownWithComObject+0x134 00000000`0f59c3c0 000007fe`f0270933 System_EnterpriseServices_ni!System.EnterpriseServices.RemoteServicedComponentProxy..ctor(System.Type, IntPtr, Boolean)+0xd5 00000000`0f59c420 000007fe`f0270213 System_EnterpriseServices_ni!System.EnterpriseServices.FastRSCPObjRef.GetRealObject(System.Runtime.Serialization.StreamingContext)+0x33 00000000`0f59c470 000007fe`f37254db System_EnterpriseServices_ni!System.EnterpriseServices.ServicedComponentProxyAttribute.CreateProxy(System.Runtime.Remoting.ObjRef, System.Type, System.Object, System.Runtime.Remoting.Contexts.Context)+0x133 00000000`0f59c500 000007fe`f37253ff mscorlib_ni!System.Runtime.Remoting.RemotingServices.SetOrCreateProxy(System.Runtime.Remoting.Identity, System.Type, System.Object)+0x9b 00000000`0f59c560 000007fe`f372466f mscorlib_ni!System.Runtime.Remoting.RemotingServices.GetOrCreateProxy(System.Runtime.Remoting.Identity, System.Object, Boolean)+0xbf 00000000`0f59c5c0 000007fe`f027050a mscorlib_ni!System.Runtime.Remoting.RemotingServices.InternalUnmarshal(System.Runtime.Remoting.ObjRef, System.Object, Boolean)+0x12f 00000000`0f59c650 000007fe`f40e3edf System_EnterpriseServices_ni!System.EnterpriseServices.ServicedComponentProxyAttribute.CreateInstance(System.Type)+0x2ba 00000000`0f59c7c0 000007fe`f5416e61 mscorlib_ni!System.Runtime.Remoting.Activation.ActivationServices.IsCurrentContextOK(System.Type, System.Object[], Boolean)+0x9ba81f
对应的.NET 托管 Call Stack:
Child-SP RetAddr Call Site 000000000f59c3c0 000007fef0270933 System.EnterpriseServices.RemoteServicedComponentProxy..ctor(System.Type, IntPtr, Boolean) 000000000f59c420 000007fef0270213 System.EnterpriseServices.FastRSCPObjRef.GetRealObject(System.Runtime.Serialization.StreamingContext) 000000000f59c470 000007fef37254db System.EnterpriseServices.ServicedComponentProxyAttribute.CreateProxy(System.Runtime.Remoting.ObjRef, System.Type, System.Object, System.Runtime.Remoting.Contexts.Context) 000000000f59c500 000007fef37253ff System.Runtime.Remoting.RemotingServices.SetOrCreateProxy(System.Runtime.Remoting.Identity, System.Type, System.Object) 000000000f59c560 000007fef372466f System.Runtime.Remoting.RemotingServices.GetOrCreateProxy(System.Runtime.Remoting.Identity, System.Object, Boolean) 000000000f59c5c0 000007fef027050a System.Runtime.Remoting.RemotingServices.InternalUnmarshal(System.Runtime.Remoting.ObjRef, System.Object, Boolean) 000000000f59c650 000007fef40e3edf System.EnterpriseServices.ServicedComponentProxyAttribute.CreateInstance(System.Type)
通过上面的调用栈信息,可以得知当serviced componnet被创建的时候,.NET RCW 逻辑会在mscorwks!RCW::CreateRCW 里调用GC.AddMemoryPressure 对可能的额外内存需要做提前预估. 当内存在不停扩展时, AddMemoryPressure会增加 Induced GC 的计数(如我们在性能监视器里发现的), 并且调用GarbageCollect。
为了解决这种问题,我们需要在应用程序里减少创建Serviced Component的次数。光在COM+里面修改成Object Pooling是不够的,因为这种Induced GC是在每次应用程序调用RCW的时候都会发生。通过研究代码和测试,如在每次调用创建Serviced Component之前使用 RemoveMemoryPressure(4004) 用来抵消RCW里的相同bytes的AddMemoryPressure(4004)可以临时解决这个问题.
最终解决方法是我们在应用程序本身创建了自己的对象池(建立一组全局对象,简单管理它们的存在周期),以彻底减少CreateInsance和CreateRCW的调用。这样处理以后,即使在大压力情况下, 程序运行也是良好的。