[ASP.NET Debugging BuggyBits读书笔记] Lab04 High CPU Hang

1. 在IIS里访问AllProducts.aspx启动w3wp.exe进程。

2. 使用TinyGet执行 tinyget srv:localhost –uri:/buggybits/AllProducts.aspx –threads:5 –loop:1

3. 在WinDbg目录执行 adplus –hang –pn w3wp.exe –quiet

4. 使用WinDbg打开第三步生成的.dmp文件,执行 .loadby sos.dll mscorwks

5. 执行 !threadpool 查看当前生成dmp时的CPU利用率 :

image

可见当前CPU利用率为100%

6. 执行 !runaway查看没有线程占用的CPU时间:

image

可见占用CPU时间最多的是线程18。

7. 切换到该线程,执行~18s。然后执行 !clrstack 查看该线程执行到了什么地方:

image

可见程序在Page_load方法里卡住了。找到这条指令所在地址 075f09c9

8. 执行 !u 075f09c9进行反汇编:

image

怀疑程序是在进行字符串构造。

9. 执行 !dso 查看该线程相关的object,找到大量的字符串对象。

10. 查看程序源代码AllProducts.aspx.cs:

image

可见程序直接使用了 + 进行字符串连接。这正好与System.String.Concat方法契合。

由于直接将字符串进行相连,会导致之前分配字符串对象的存储空间被废弃,CLR必须为新的字符串对象分配新的空间,这就导致CLR忙于分配字符串空间,造成high CPU。比如下例:

class Program
{
    static void Main(string[] args)
    {
        string s = null;
        for (int i = 0; i < 100000; i++)
        {
            s = s + i.ToString();
        }
    }
}
运行之后可以直接在Task Manager里看到CPU占用率的直线上升。

对此的解决办法是使用StringBuilder:

class Program
{
    static void Main(string[] args)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100000; i++)
        {
            sb.Append(i.ToString());
        }
    }
}

这样当有新的String声称之后,旧有的string会在原对象大小的基础分配一个大小为原对象大小两倍的对象。这样就避免CLR反复消耗于分配新的对象。

20110218更新:

一、 如果怀疑是High CPU,则使用!threadpool查看究竟多高,再使用!runaway查看哪个thread占用的CPU最高,第三步使用!threads查看各个托管thread的类型。

二、使用Performance Monitor的.CLR Memory对象和Process对象,进行监测,查看的counter包括#Gen 0/1/2 collections,Induced GC,Processor Time和Time in GC。得到如下图像:

  

  1. 从Processor Time可以看到CPU利用率得到100%,而从%Time in GC(途中白色部分)可以得知GC垃圾回收过程占用了大部分的CPU使用。GC的平均利用率在65%,但是正常情况下应该不会超过5%。

  2. Gen 0/1/2 Collections的数量基本相等,比值约为1:1:1;但是正常情况下,其比值应约为100:10:1。

  3. 导致Gen 0/1/2 Collections对象数量近似的原因只有一个,那就是GC回收过程被反复触发。其中触发有两种情况,一种是手动执行GC回收,另外一种是在Gen0中的对象太大,导致Gen0空间很快不足,GC被触发,成了Gen1,而Gen1很快又给弄成了Gen2,所以三代对象基本相等。

  4. 从Induced GC可以得知并没有手动触发存在,从而判定是Object太大。

三、切换到CPU占用率高的线程,执行!clrstack,发现该线程在做如下工作:

  0:016> !clrstack
OS Thread Id: 0x1948 (16)
ESP       EIP    
0252f208 7d61c828 [RedirectedThreadFrame: 0252f208]
0252f24c 79363058 System.String.wstrcpy(Char*, Char*, Int32)
0252f258 7936d6e7 System.String.FillStringChecked(System.String, Int32, System.String)
0252f278 79378b9f System.String.ConcatArray(System.String[], Int32)
0252f28c 79378af4 System.String.Concat(System.Object[])
0252f2a0 0fe90a2a AllProducts.Page_Load(System.Object, System.EventArgs)
0252f2d8 66f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0252f2e8 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0252f2f8 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0252f308 6613cb50 System.Web.UI.Control.LoadRecursive()
0252f31c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0252f518 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0252f550 6614d80f System.Web.UI.Page.ProcessRequest()
0252f588 6614d72f System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0252f590 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0252f5a4 0fe90205 ASP.allproducts_aspx.ProcessRequest(System.Web.HttpContext)
0252f5a8 65fe6bfb System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
0252f5dc 65fe3f51 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
0252f61c 65fe7733 System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)
0252f66c 65fccbfe System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
0252f688 65fd19c5 System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
0252f6bc 65fd16b2 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
0252f6c8 65fcfa6d System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
0252f8d8 79f047fd [ContextTransitionFrame: 0252f8d8]
0252f90c 79f047fd [GCFrame: 0252f90c]
0252fa68 79f047fd [ComMethodFrame: 0252fa68]

因而怀疑这个巨大的object是System.String类型。

  执行命令 !dumpheap -type System.String -min 85000:

找到heap中相关的string对象,通过!do查看其内容,便可得知程序是在构造一个超长的字符串。

posted on 2011-01-17 22:52  李志鹏  阅读(524)  评论(0编辑  收藏  举报

导航