• 找到一些好的文章。放在此处慢慢学习


    C#代码如下:

    private void button2_Click(object sender, System.EventArgs e)
    {
            System.Threading.Thread thread = new System.Threading.Thread( new                       System.Threading.ThreadStart(this.ShowOCX2));
            thread.SetApartmentState(System.Threading.ApartmentState.STA);
            thread.Start();
    }

    private void ShowOCX2()
    {
            Form2 form = new Form2();
            form.ShowDialog();
    }

    Form2中是放入了一个ocx(delphi写的一个空ocx,任何自己的代码都没有)。

    现在症状是,点击button2之后,出现了form2,直接关闭form2;再次点击button2,程序立刻crash掉。

    抓dump(启用了pageheap),发现下面代码:(注意红色的)

    Thread 4
    Current frame: OCX2Proj1+0x3402
    ChildEBP RetAddr Caller,Callee
    0513f808 7c9237bf ntdll!ExecuteHandler2+0x26
    0513f82c 7c92378b ntdll!ExecuteHandler+0x24, calling ntdll!ExecuteHandler2
    0513f850 7c957860 ntdll!RtlDispatchException+0xb1, calling ntdll!RtlpExecuteHandlerForException
    0513f874 060e3407 OCX2Proj1+0x3407
    0513f880 7c9211a7 ntdll!LdrpCallInitRoutine+0x14
    0513f884 7c93e6f4 ntdll!LdrUnloadDll+0x41c, calling ntdll!LdrpCallInitRoutine
    0513f888 7c80abf7 kernel32!FreeLibrary+0x3f, calling kernel32!LdrUnloadDll
    0513f890 769c3456 ole32!CClassCache::CFinishComposite::Finish+0x1d
    0513f894 7c988c37 ntdll!RtlpDphNeedToTrimDelayedFreeQueue+0x3f, calling ntdll!RtlLeaveCriticalSection
    0513f8a8 7c98acda ntdll!RtlpDphNormalHeapFree+0xb1, calling ntdll!RtlpDphNeedToTrimDelayedFreeQueue
    0513f8bc 7c98770b ntdll!RtlpDebugPageHeapLeaveCritSect+0x10, calling ntdll!RtlLeaveCriticalSection
    0513f8c8 7c98af7d ntdll!RtlpDebugPageHeapFree+0x192, calling ntdll!RtlpDebugPageHeapLeaveCritSect
    0513f8dc 7c92eafa ntdll!KiUserExceptionDispatcher+0xe, calling ntdll!RtlDispatchException
    0513fbdc 060e3402 OCX2Proj1+0x3402 ====> Exception cxr@513f910
    0513fb00 7c9306eb ntdll!RtlAllocateHeap+0xeac, calling ntdll!_SEH_epilog
    0513fc70 7c9211a7 ntdll!LdrpCallInitRoutine+0x14
    0513fc90 7c93e6f4 ntdll!LdrUnloadDll+0x41c, calling ntdll!LdrpCallInitRoutine
    0513fcf4 7c939213 ntdll!LdrShutdownThread+0xd7, calling ntdll!LdrpCallInitRoutine
    0513fcf8 7c80c096 kernel32!ExitThread+0x3e, calling kernel32!LdrShutdownThread
    0513fd70 77d193e9 user32!NtUserPeekMessage+0xc

    然后切换进去
    0:004> .cxr 513f910
    eax=f0f0f0f0 ebx=0000000f ecx=00000000 edx=f0f0f001 esi=06146590 edi=06149638
    eip=060e3402 esp=0513fbdc ebp=0513fbf0 iopl=0 nv up ei ng nz na pe nc
    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286
    OCX2Proj1+0x3402:
    060e3402 8b08 mov ecx,dword ptr [eax] ds:0023:f0f0f0f0=????????
    0:004> kb
    *** Stack trace for last set context - .thread/.cxr resets it
    ChildEBP RetAddr Args to Child
    WARNING: Stack unwind information not available. Following frames may be wrong.
    0513fbf0 060e3f10 0513fc64 060e3cff 0513fc10 OCX2Proj1+0x3402
    0513fc10 060e4246 0513fc70 0513fc60 06149664 OCX2Proj1+0x3f10
    0513fc70 7c9211a7 060e0000 00000000 00000000 OCX2Proj1+0x4246
    0513fc90 7c93e6f4 061467b0 060e0000 00000000 ntdll!LdrpCallInitRoutine+0x14
    0513fd88 7c80abf7 060e0000 0513fdd0 0513fdf4 ntdll!LdrUnloadDll+0x41c
    0513fd9c 769c3442 060e0000 0513fe18 769c3456 kernel32!FreeLibrary+0x3f
    0513fda8 769c3456 0513fddc 76ab67e0 00000000 ole32!CClassCache::CDllPathEntry::CFinishObject::Finish+0x2f
    0513fdbc 769b2557 76991ab0 00000000 00000000 ole32!CClassCache::CFinishComposite::Finish+0x1d
    0513fe18 769b2243 0133d878 00000080 00000001 ole32!CClassCache::CleanUpDllsForApartment+0x1b5
    0513fe44 769b2171 00000000 0513fe90 76ab67e8 ole32!FinishShutdown+0xcd
    0513fe60 769af221 00000000 00000000 0133d878 ole32!ApartmentUninitialize+0x7e
    0513fe78 769aee88 0513fe90 00000000 00000001 ole32!wCoUninitialize+0x41
    0513fe94 769c31f0 76990000 769ad1a2 0513fec4 ole32!CoUninitialize+0x5b
    0513fe9c 769ad1a2 0513fec4 769ad141 76990000 ole32!DoThreadSpecificCleanup+0x47
    0513fea4 769ad141 76990000 00000003 00000000 ole32!ThreadNotification+0x37
    0513fec4 769ad0e9 76990000 00000003 00000000 ole32!DllMain+0x147
    0513fee4 7c9211a7 76990000 00000003 00000000 ole32!_DllMainCRTStartup+0x52
    0513ff04 7c939213 769ad0a1 76990000 00000003 ntdll!LdrpCallInitRoutine+0x14
    0513ff7c 7c80c096 00000000 00000000 01321d40 ntdll!LdrShutdownThread+0xd7
    0513ffb4 7c80b688 00000000 00000000 00000000 kernel32!ExitThread+0x3e

    最上面三行就是delphi写的那个空白ocx,下面三个是各自的代码:
    0:004> u OCX2Proj1+0x4246
    OCX2Proj1+0x4246:
    060e4246 ?? ???
    ^ Memory access error in 'u OCX2Proj1+0x4246'


    0:004> u OCX2Proj1+0x3f10
    OCX2Proj1+0x3f10:
    060e3f10 ?? ???
    ^ Memory access error in 'u OCX2Proj1+0x3f10'

    0:004> u OCX2Proj1+0x3402
    OCX2Proj1+0x3402:
    060e3402 8b08 mov ecx,dword ptr [eax]
    060e3404 ff51fc call dword ptr [ecx-4]
    060e3407 c3 ret
    060e3408 53 push ebx
    060e3409 56 push esi
    060e340a 57 push edi
    060e340b 89c3 mov ebx,eax
    060e340d 89d7 mov edi,edx

    0:004> d [ecx-4]
    fffffffc ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000000c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000001c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000002c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000003c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000004c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000005c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????
    0000006c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ????????????????

    郁闷之极,好像用VB写一个空OCX放到form上,然后thread调用起来,也有这个问题。哪位老大见过这个问题?貌似和Appartment有关?

    发表于 @ 14:32 | (阅读:4763;评论:7)

    2007年1月19日

    第一步,在业务高峰期抓取样本数据(2个小时左右)。采用的工具是sqlserver自带的profiler,也叫事件探查器,如下图:

    进入后,点击最左面的按钮,建立一个新的跟踪:

    登录需要用DBO权限,所以可以用sa登录,也可以用windows集成验证方式(如果当前登录的就是sqlserver的话)

    新建跟踪,一共有4个tab页进行配置,首先看第一个。跟踪名称不用更改,默认的即可。保存一共有两种方式,一是文件,扩展名是.trc(这种方式方便你把客户那里的跟踪结果发给你),其二是数据库中的表。

    为了分析方便,我们把它另存为表。此时sql提示你重新进行登录,这里我们把表保存到master中

    假设表名字叫做jq(如果有重复的,系统会提示是否覆盖)

    确定后回到了刚才的第一个tab页中:

    然后切换到第二个选项卡中:

    左面列出了各种事件类(Event Class),右面是当前已有的事件类。对于性能调优,我们不需要安全审核、会话信息,点击删除按钮即可:

    继续切换到第三个tab页上,这里的数据列默认就够了,当然,如果你看着不顺眼,可以把Appname/NT username等都删除。

    最后一个tab页上,我们需要把系统自己产生的事件ID屏蔽掉:

    把那个排除系统ID进行check即可,如下图:

    所有项目配置好后,点击“运行”按钮。持续运行两个小时左右即可(业务高峰期,能典型的反应客户最近一段时间内的业务模式)

    好了,第一步的准备工作完成了,等待一段时间后,我们开始检查刚才自动保存到master中的表jq。

    第二步,开始查找影响速度的地方。

    打开查询分析器(sql analyzer),登录到master中,从 表jq里面按照I/O倒序,读取若干个sql。根据我的习惯,一般是读取1000条记录。为什么根据I/O来找呢,而不是根据时间来找呢?原因很简单,一句SQL执行,“稳定”的是I/O,而duration是一个不稳定的因素。我们进行sql调优的目的,就是降低I/O成本,从而提高效率。(一般而言,I/O降低了,duration自然就会降低)详细内容,参考我以前的post:http://blog.joycode.com/juqiang

    执行完成后,我们仔细看下面的输出。

    1、 XL_TALLY_Proc04这个sp的reads最大,将近100w,duration也达到了25秒多。

    2、 Erp_IM_GMBill_GetBill这个sp的I/O不算大,才7w,duration平均都在1秒多点。但是这个sp执行的次数非常多。

    经过询问客户,XL_TALLY_Proc04这个sp执行的频度很低,一天也就一两次,但是Erp_IM_GMBill_GetBill大概5分钟就要一次。这样整体I/O就占用的非常大。

    所以这里我们要重点分析Erp_IM_GMBill_GetBill这个sp,而不是第一个!

    总结一个原则就是:调整的重点是客户最关心的内容,是执行频度最高、看起来I/O又比较大的那种。I/O最大的,不一定是我们要优先解决的内容。

    第三步,开始分析刚才看到的那个语句。既然我们要分析I/O,那么就要把I/O打开,这样每次调整sql,我们都能随时看到I/O的变化情况。这句很有用处地:set statistics io on

    单纯看I/O变化,我们会晕倒的。因为我们不知道自己做的任何改动,对I/O是如何产生影响的。所以,还要看sql的执行计划是怎佯的。 在查询分析器中,我们按Ctrl+K,或者如下图的菜单,check上即可。

    好了,准备工作都做好了,下面开始干活了。

    我们首先看sql语句的调优,假设下面这条sql语句性能低下:

    上面的sql一共读取了6636条数据,逻辑读是1126。那么这个I/O是否合理呢?大了还是小了?还有改进的余地吗?我们看执行计划:

    哦,一共4个咚咚在里面。Index seek的成本占了2%, index scan的占了47%,hash match占了51%,select最终是0%。我们应该牢记第二个原则,所有的index,尽可能的都走index seek。

    我们看一下billsoflading的索引信息:

    当前索引为什么走scan,这里就不说了,感兴趣的可以随便找一本介绍数据库索引的书籍来看看即可。根据我以前那篇blog的描述,我们知道应该建立一个复合索引(也叫convered index):boldate+companyid+bolcode

    然后我们重新执行sql,看看I/O变化情况:

    Ooh,非常cool!logical reads降低到了50。为什么会这样呢?我们看一下执行计划:

    原来是index scan变成了index seek,效率自然大大的提升!

    Sql语句在index上调优的方法,基本就是这样。我们继续看sp的。

    对于sp的调优,有一点是和sql调优不同的:sp内部的逻辑处理可能非常复杂。单纯从查询分析器中,我们无法得知哪一小块的sql执行的I/O最大,我们只能看到一个总体的描述。所以,我们要知道sp内部的信息。

    首先,了解自己当前的spid是多少。一种方法是select @@spid,另一种方法是看查询分析器下面的status bar的信息。

    Ooh,我的spid是101。(上图的最下面那个tips)

    然后我重新打开profiler(事件探查器),重新建立一个跟踪,这里面要修改第二个tab页的信息,把左面事件列“存储过程”中的SmtpCompleted加上

    增加后的样子如下:

    然后修改第4个tab页,把刚才看到的spid=101的信息填上:

    点击运行后,这样profiler只能抓到在查询分析器中,spid=101那个窗口发送的sql。我们切换回查询分析器,执行有问题的sp,执行完成后,我们再回到profiler,点停止按钮。一个sp内部所有执行的sql,都被分开了!

    这次的结果假设保存在了jq2表中,我们把所有执行的小片sql都列出来:

    第一个是sp执行后的总体结果,I/O为62328,就是这个sp自己的。第二个是向临时表中插入数据,I/O为61514,我们很容易看到,这一句占用了整个sp的大概95%以上的成本。如果我们把这句insert into #temptable搞定,整个sp的成本自然就下来了。所以我们需要把这句insert搞出来。

    但是慢着!default情况下,sqlserver的results只显示很少的字符,第二行的sql,我们根本抓不全的,所以我们需要修改一下设置。在查询分析器的工具-选项菜单中,切换到“结果”这个tab页,修改每列最多字符个数为8192(这是最大的允许值),然后点击“确定”按钮,重新从jq2中读取信息。也许你会问,如果某个sql特别长,怎么办?其实很简单,在你的代码中把这句sql单独写到log中,或者直接修改sp,把这句print出来即可。

    Ok,我们把这句insert sql抓下来后,放到查询分析器中。因为temptable我们没有它的结构,所以我们把insert部分注释掉,看后面的select语句。执行后,ooh,在goodsmovement表上的成本是57834。

    老办法,我们继续看执行计划:

    其实,现在又回归到了sql调优的步骤,下面的工作我就不写啦!

    这个步骤,看起来很简单,希望大家对于sql调优(索引部分)心中都有这么一个概念,知道第一步作什么,第二步作什么。还是那句话,索引调优,基本上是最简单的。但是貌似简单的东西,我们越应该重视。你随便找一个应用跟踪一下,各种效率低下的索引,会让你实在#¥*#(**……¥

    最后,看看偶的桌面,14集何时能够出来呢?期待中……

    发表于 @ 13:06 | (阅读:6508;评论:16)

    2007年1月11日

    这个回复是针对我上一个post大家的热情回复而写的。安全问题容易被人忽视,倒不是说不重视,而是自己苦心研究出来的东西,很容易从其他人另一个角度被攻破。

    尤其在.NET上,这种问题之所以出现,我认为是大家对于.NET的视角不同造成的。.net让快速开发确实达到了,但是也让程序员丧失了“警惕性”。

    那位兄弟说的破解成本确实是问题。成本太高,破解没有意义了。但是致命的是,我接触到的这些国内ERP厂商,对于序列号控制这部分,实在是太脆弱了,脆弱到破解成本几乎就是0 - 虽然后面有大型应用、技术支持这个说法。

    我印象中看卡巴斯基的书上说,最好的“提高破解难度”的方法是在native code中进行SEH处理,把核心代码放到catch里面。据说这样会让cracker晕倒………………

    至于加壳与否,我认为只要想做,有足够的耐心,把内存都dump出来,象dos下面破解,一样的。

    发表于 @ 11:38 | (阅读:4194;评论:13)

    2007年1月10日

    业界对于Java/.NET程序的一个批评就是其安全性。由于IL的特点,各种reflector很容易把代码搞出来。混淆器,貌似一个很常用的功能吧?今天看某个软件,与我们的应用有些类似,所以想借鉴一下。安装好之后,发现有一个License Manager,两个按钮,一个是生成申请信息,一个是导入序列号。(通用的做法,我们的应用也是这么做的)
    用reflector打开后,ooh,大概80%的信息都被混淆掉了。field/method的名称,都成了稀奇古怪的文字。还有,我的File Disassembler plug-in居然不能用了,于是只能把所有的method/field信息都paste到notepad上面。(下面所有代码,出于众所周知的原因,我又做了第二次“混淆”,所有的变量/方法名称,都是随意写的)

    映入眼帘的第一个method是.ctor,我们知道这个是缺省的构造方法。看代码,如下:

    public SomeConstructor()
    {
          this.Abcdef123456();
    }

    上面的方法就是被混淆过了,虽然没意义,但是我们能够“猜测”出来!
    进入上面的Abcdef123456方法,如下:

    private void Abcdef123456()
    {
          this.fedcba654321 = new Container();
          Button button1 = new Button();
          Button button2 = new Button();
          ...
    }

    好熟悉吧!这就是InitializaComponent方法,查找所有的Abcdef123456,统一替换为这个新名字就可以了。同样的道理,通过看程序运行时候的界面、在查找代码,能更正90%以上的混淆名称,变成一个清晰的表达(虽然不一定和源代码相同)

    继续看reflector的结果,有两个分别是Button1_Click和Button2_Click方法。
    先看第一个,里面有段代码如下:
    byte[] buffer1 = MD5.Encrypt(this.B33498573V + '\v' + this.J39475SDf, false);
    stream1.Write(buffer1, 0, buffer1.Length);
    byte[] buffer2 = MD5.Encrypt(new UnicodeEncoding().GetBytes(text1));
    stream1.Flush();
    if (SomeCondition == true)
    {
          SaveCPUandDISKInfo(this.B33498573V.Text , this.J39475SDf.Text);
    }
    InfoShow("\u751f\u6210");

    看起来似乎很麻烦?晕,居然还有MD5,还有一些复杂的逻辑判断。怎么搞?分析那个MD5怎么加密的吗?没意义!
    首先我们看最后面的InfoShow,我们都知道,这是Unicode编码方式下的文字,简单的用Console.WriteLine出来即可,上面的两个字是“生成”,于是我们猜测,这个和序列号可能相关。很自然的,我们会想到,对于注册的话,会有“成功”或者“失败”。前者转换为unicode是:\u6210\u529f。好吧,我们搜索“\u6210\u529f”。幸运!我们在Button2_Click方法中找到了这个字符串。这个方法代码如下:
    if (MD5.CheckRegisterKey(this.FileWillImported.Text)){
        File.Copy();
        ReadRegistry();
        GetCPUandDISKinfoFromDatabase();
        if(ResultFromUpline() == true){
            SetCPUandDISKinfoIntoDatabase();
        }
        InfoShow("\u6210\u529f");
    }
    else{
        InfoShow("\u975e\u6cd5");
    }

    我们看到上面的代码,进行了一些复杂的操作,包括什么MD5/SHA/RSA等,最后成功才显示出来一个"\u6210\u529f"。作为游戏,我们继续寻找“失败”的unicode bytes,ooh,没有找到!但是从上面的代码我们可以看出,最后的else是有问题的。同样的Console.WriteLine一下,显示出来的是“非法”这两个汉字。

    下面的工作就简单了,ildasm /out方式,把IL弄出来,直接调用SetCPUandDISKinfoIntoDatabase方法,屏蔽掉所有的判断即可。
    再用reflector看,该文件并没有任何对于非托管代码的refrence,安装目录下面也没有任何非托管dll,所以基本确定就是这样子了。

    总结一下,我们用到的方法,都是20年前的方法。找到关键字符串,查找上下文可疑代码,把相关的判断信息都屏蔽掉,工作就完成了。那些花哨的各种“非对称算法”,没有任何意义。因为在加密解密工作中,流程的转向是重要的,流程本身,是无意义的。这里没有我们10年前看到的那些让人目眩的反侦查手法。

    前段时间帮一朋友看过一个.net的应用,用usb key来做的,调用了自己写的Win32 API。同样的弊病,写了非常复杂的加密、解密、反跟踪、反调试处理,可惜的是,这一切都是在Win32中作的,.NET代码只是简单的做了CALL。屏蔽掉了200多行的IL,直接return true,唔,这个世界清净了………………

    (再次声明,本文纯属对于.NET简单混淆的一个讨论,文中对于该软件的任何可以识别的特征,都进行了“混淆”)
    (对于破解和反汇编,可以看卡巴斯基本人写的那本黑客反汇编揭秘,本人对于汇编纯属文盲)

    反过头来,我们考虑本文标题的内容,.NET或者J2EE代码应该如何保护?混淆是必要的,但太脆弱。反跟踪、反调试,我记着有高手在作,但是我不认为这能解决根本问题。不管多复杂的加密手段,被一些低级的代码调用的时候,总能很快的被搞定,如上面分析的那些代码。

     

    发表于 @ 14:57 | (阅读:4121;评论:8)

    2007年1月4日

    很好的日子,2007年1月1日收到了确认通知。这是偶第二次通过Solution Architect的MVP,很是高兴。

    发表于 @ 10:37 | (阅读:4565;评论:9)

    2006年12月29日

    貌似PageParser的大量调用,会导致大量的小size的dll、众多memory hole的出现。得到eparg的指教,越来越感觉是这个样子。据joycode老大说,只有2.0这个样子,只有特定的这一个app这个样子。

    准备写一个.net 2的测试程序,看这个hole是不是确实很明显?

    (详情等下文)

    发表于 @ 1:03 | (阅读:3852;评论:3)

    2006年12月22日

    Session,Session,Session!(请耐心阅读………………)

    地球人都知道,asp.net中有三种方式存放我们的session objects。In Proc模式,在cache中存放对象。StateServer在State Service中存放,最后一种是存放在SQL Server中。对于In Proc模式,太多的session对象,意味着高内存占用;对于后两者,意味着序列化和反序列化的性能损失。
    Session存放的东西太多,不一定意味着性能的问题,但这依赖于你往session里面存放的东西。
    让我们假设一个场景,你开发了一个网上商店,有很少的人在用,所以你只用了一台web server,使用了In Proc模式,你的程序对每个用户的订单,都存放了一些dataset。
    突然,你的程序出名了!一个大公司来找你,于是,你增加了N台web server,同时把Session State也修改成了SQL Server模式。

    问题来了!
    asp.net进程,内存占用很好(800MB – 1GB),有时候会提示OutOfMemory异常,或者提示.net进程被recycled了。dump的大小为1,473,913 bytes,所以,大概就是1.4G。首先,我们看看托管堆上面都有些啥东西?

    0:023> !eeheap -gc
    Number of GC Heaps: 2
    ------------------------------
    Heap 0 (0x000b7198)
    generation 0 starts at 0x022104d4
    generation 1 starts at 0x022037c0
    generation 2 starts at 0x02170030
    ephemeral segment allocation context: none
     segment    begin       allocated     size
    0x2170000 0x2170030  0x224a4e0 0xda4b0(894,128)
    Large object heap starts at 0x0a170030
     segment    begin       allocated     size
    0x0a170000 0x0a170030  0x0acf0b20 0x00b80af0(12,061,424)
    0x0d490000 0x0d490030  0x0e3d2450 0x00f42420(16,000,032)
    0x12010000 0x12010030  0x12f52460 0x00f42430(16,000,048)
    0x13010000 0x13010030  0x13f52460 0x00f42430(16,000,048)
    0x15010000 0x15010030  0x15f52460 0x00f42430(16,000,048)
    0x1a010000 0x1a010030  0x1af52460 0x00f42430(16,000,048)

    0x71ca0000 0x71ca0030  0x72be2470 0x00f42440(16,000,064)
    0x748b0000 0x748b0030  0x757f2470 0x00f42440(16,000,064)
    0x7d0e0000 0x7d0e0030  0x7d881250 0x007a1220(8,000,032)
    Heap Size  0x2d5b4e10(760,958,480)
    ------------------------------
    Heap 1 (0x000ede88)
    generation 0 starts at 0x06249b58
    generation 1 starts at 0x0623e190
    generation 2 starts at 0x06170030
    ephemeral segment allocation context: none
     segment    begin       allocated     size
    0x6170000 0x6170030  0x6283b64 0x113b34(1,129,268)
    Large object heap starts at 0x0b170030
     segment    begin       allocated     size
    0x0b170000 0x0b170030  0x0b9f1c90 0x00881c60(8,920,160)
    0x0c3e0000 0x0c3e0030  0x0d322460 0x00f42430(16,000,048)
    0x0e490000 0x0e490030  0x0f3d2460 0x00f42430(16,000,048)
    0x11010000 0x11010030  0x11f52460 0x00f42430(16,000,048)
    0x14010000 0x14010030  0x14f52450 0x00f42420(16,000,032)
    0x16010000 0x16010030  0x16f52480 0x00f42450(16,000,080)
    0x17010000 0x17010030  0x17b81b60 0x00b71b30(12,000,048)
    0x18010000 0x18010030  0x18b81b60 0x00b71b30(12,000,048)
    0x19010000 0x19010030  0x19f52460 0x00f42430(16,000,048)
    0x1b010000 0x1b010030  0x1bf52460 0x00f42430(16,000,048)

    0x61010000 0x61010030  0x61f52470 0x00f42440(16,000,064)
    0x62db0000 0x62db0030  0x63cf2470 0x00f42440(16,000,064)
    0x657e0000 0x657e0030  0x66722470 0x00f42440(16,000,064)
    0x685c0000 0x685c0030  0x69502470 0x00f42440(16,000,064)
    0x6e110000 0x6e110030  0x6ec81b70 0x00b71b40(12,000,064)
    0x72ca0000 0x72ca0030  0x73be2470 0x00f42440(16,000,064)
    0x77ff0000 0x77ff0030  0x78f32470 0x00f42440(16,000,064)
    0x7e0e0000 0x7e0e0030  0x7f022470 0x00f42440(16,000,064)
    Heap Size  0x286a4124(678,052,132)
    ------------------------------
    GC Heap Size  0x55c58f34(1,439,010,612)

    !eeheap –gc告诉我们两个事情:
    a) the GC Heap大小在1.4G左右,和上面的总内存占用非常接近。这说明大部分内存占用,都在托管堆上。
    b) 大部分内存占用都在LargeObjects上面(>85000bytes的东西)

    很自然的,我们要看看在heap里面的LO到底都是啥东西?
    0:023> !dumpheap -min 85000 -stat
    Using our cache to search the heap.
    Statistics:
            MT      Count     TotalSize Class Name
    0x000eda20          1       920,144      Free
    0x01b2209c         33   132,000,528 System.Object[]
    0x01b226b0        163 1,304,001,956 System.Int32[]
    Total 197 objects, Total size: 1,436,922,628
    大部分内存被Int32数组占用了,还有132M被一个对象数组占用了。注意啊!这里的132M和那个1.3G,都只是object本身的大小,而不包括存储在Object数组里面的对象的大小。如果要看详细情况,我们需要跑一下!objsize才可以。
    0:023> !dumpheap -min 85000
    Using our cache to search the heap.
       Address         MT     Size  Gen
    0x0b170030 0x000eda20  920,144   -1      Free
    0x0a920210 0x01b2209c 4,000,016   -1 System.Object[]
    0x247b1250 0x01b2209c 4,000,016   -1 System.Object[]
    0x277b1250 0x01b2209c 4,000,016   -1 System.Object[]
    0x287b1250 0x01b2209c 4,000,016   -1 System.Object[]

    0x51af0030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x52291250 0x01b226b0 8,000,012   -1 System.Int32[]
    0x53b40950 0x01b226b0 8,000,012   -1 System.Int32[]
    0x56620030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x57620030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x5a440030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x5abe1250 0x01b226b0 8,000,012   -1 System.Int32[]
    0x5c660030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x5ce01250 0x01b226b0 8,000,012   -1 System.Int32[]
    0x61010030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x617b1250 0x01b226b0 8,000,012   -1 System.Int32[]
    0x62db0030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x63551250 0x01b226b0 8,000,012   -1 System.Int32[]
    0x657e0030 0x01b226b0 8,000,012   -1 System.Int32[]
    0x65f81250 0x01b226b0 8,000,012   -1 System.Int32[]
    注意Gen这一列,这些object都没有被GC,为啥?随便找一个,看看吧!
    0:023> !gcroot 0x72ca0030
    Scan Thread 16 (0xbd8)
    ESP:1a3f5e0:Root:0x6280d38(System.Web.HttpContext)->
    0x62809ec(System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6)->
    0x61b7030(System.Web.HttpWorkerRequest/EndOfSendNotification)->
    0x61b47d4(System.Web.HttpRuntime)->
    0x61b4ca0(System.Web.Caching.CacheMultiple)->0x61b4cc4(System.Object[])->
    0x61b4cdc(System.Web.Caching.CacheSingle)->
    0x61b4dac(System.Web.Caching.CacheExpires)->0x61b4ff8(System.Object[])->
    0x61b5ab8(System.Web.Caching.ExpiresBucket)->
    0x222e63c(System.Web.Caching.ExpiresEntry[])->
    0x220292c(System.Web.Caching.CacheEntry)->
    0x22028fc(System.Web.SessionState.InProcSessionState)->
    0x2202690(System.Web.SessionState.SessionDictionary)->
    0x220273c(System.Collections.Hashtable)->
    0x2202770(System.Collections.Hashtable/bucket[])->
    0x2202810(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)->
    0x72ca0030(System.Int32[])
    Scan Thread 20 (0x89c)
    Scan Thread 22 (0xa5c)
    Scan HandleTable 0xdc9d8
    Scan HandleTable 0xea6e8
    Scan HandleTable 0x1531e8
    看这条链,我们发现Int32被Cache root了(看GC的原理就知道这句话啥意思了),看上面的InProcSessionState,我们知道现在是InProc模式。
    继续看……
    0:023> !dumpaspnetcache -stat
    Going to dump the ASP.NET Cache.
            MT      Count     TotalSize Class Name
    0x0211cc9c          1            20 System.Web.Security.FileSecurityDescriptorWrapper
    0x020c242c          2            56 System.Web.UI.ParserCacheItem
    0x0206c66c          5           260 System.Web.Configuration.HttpConfigurationRecord
    0x0c2e7014          1           316 System.Web.Mobile.MobileCapabilities
    0x79b94638          4           376 System.String
    0x0c2eaeb4        151         7,248 System.Web.SessionState.InProcSessionState
    Total 164 objects, Total size: 8,276
    好,我们找到了151个Session对象。
    0:023> .foreach (obj {!dumpheap -mt 0x0c2eaeb4 -short}){!objsize ${obj}}
    sizeof(0x22028fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2202a10) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2202cfc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2202fe8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x22032d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x22035c0) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2203a38) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2203d24) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x2204010) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)

    sizeof(0x6248080) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6248334) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x62485e8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x624a84c) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x624d7b8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6250724) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6253690) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x62565fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6259568) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x625c4d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x625f440) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x62623ac) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6265318) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x6268284) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
    sizeof(0x626b1b8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
    看到了!151个session对象,每个拿到了8-12M左右的数据。就是这个鸟玩意搞的内存有问题啦!
    随便找一个,看看什么在里面???
    0:023> !do 0x626b1b8
    Name: System.Web.SessionState.InProcSessionState
    MethodTable 0x0c2eaeb4
    EEClass 0x0c1c5660
    Size 48(0x30) bytes
    GC Generation: 0
    mdToken: 0x02000132  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
    FieldDesc*: 0x0c2eae0c
            MT      Field     Offset                 Type       Attr      Value Name
    0x0c2eaeb4 0x40009f2      0x4                CLASS   instance 0x06269f74 dict
    0x0c2eaeb4 0x40009f3      0x8                CLASS   instance 0x00000000 staticObjects
    0x0c2eaeb4 0x40009f4      0xc         System.Int32   instance 20 timeout
    0x0c2eaeb4 0x40009f5     0x18       System.Boolean   instance 0 isCookieless
    0x0c2eaeb4 0x40009f6     0x10         System.Int32   instance 0 streamLength
    0x0c2eaeb4 0x40009f7     0x19       System.Boolean   instance 0 locked
    0x0c2eaeb4 0x40009f8     0x1c            VALUETYPE   instance start at 0x0626b1d4 utcLockDate
    0x0c2eaeb4 0x40009f9     0x14         System.Int32   instance 1 lockCookie
    0x0c2eaeb4 0x40009fa     0x24            VALUETYPE   instance start at 0x0626b1dc spinLock
    每个InProcSessionState对象都有一个叫做_entriesArray的dict成员,装有实际的session对象列表。就是上面偏移为0x04的那行。
    0:023> !do 0x06269f74
    Name: System.Web.SessionState.SessionDictionary
    MethodTable 0x0c2e0c54
    EEClass 0x0c1c1308
    Size 44(0x2c) bytes
    GC Generation: 0
    mdToken: 0x0200013b  (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
    FieldDesc*: 0x0c2e0b30
            MT      Field     Offset                 Type       Attr      Value Name
    0x0206b338 0x4000a8b     0x24       System.Boolean   instance 0 _readOnly
    0x0206b338 0x4000a8c      0x4                CLASS   instance 0x06269fb8 _entriesArray
    0x0206b338 0x4000a8d      0x8                CLASS   instance 0x06269fa0 _hashProvider
    0x0206b338 0x4000a8e      0xc                CLASS   instance 0x06269fac _comparer
    0x0206b338 0x4000a8f     0x10                CLASS   instance 0x0626a020 _entriesTable
    0x0206b338 0x4000a90     0x14                CLASS   instance 0x00000000 _nullKeyEntry
    0x0206b338 0x4000a91     0x18                CLASS   instance 0x00000000 _keys
    0x0206b338 0x4000a92     0x1c                CLASS   instance 0x00000000 _serializationInfo
    0x0206b338 0x4000a93     0x20         System.Int32   instance 4 _version
    0x0c2e0c54 0x4000a0f     0x25       System.Boolean   instance 1 _dirty
    0x0c2e0c54 0x4000a0e        0                CLASS     shared   static s_immutableTypes
        >> Domain:Value 0x000dad08:NotInit  0x00104f30:0x021be0dc <<
    同样,还是看_entriesArray,偏移为0x04的那行
    0:023> !do 0x06269fb8
    Name: System.Collections.ArrayList
    MethodTable 0x79ba2ee4
    EEClass 0x79ba3020
    Size 24(0x18) bytes
    GC Generation: 0
    mdToken: 0x02000100  (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
    FieldDesc*: 0x79ba3084
            MT      Field     Offset                 Type       Attr      Value Name
    0x79ba2ee4 0x4000362      0x4                CLASS   instance 0x06269fd0 _items
    0x79ba2ee4 0x4000363      0xc         System.Int32   instance 3 _size
    0x79ba2ee4 0x4000364     0x10         System.Int32   instance 3 _version
    0x79ba2ee4 0x4000365      0x8                CLASS   instance 0x00000000 _syncRoot
    哦,Name是ArrayList,好,我们继续看0x04对应的那个_items吧!
    0:023> !do -v 0x06269fd0
    Name: System.Object[]
    MethodTable 0x01b2209c
    EEClass 0x01b22018
    Size 80(0x50) bytes
    GC Generation: 0
    Array: Rank 1, Type CLASS
    Element Type: System.Object
    Content: 16 items
    ------ Will only dump out valid managed objects ----
       Address         MT Class Name
    0x0626a81c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
    0x0626a82c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
    0x0626a83c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
    ----------
    对于每个对象,我们打印出0x04位置的Name和0x08位置的Size。
    0:023> .foreach (obj {!do -v 0x06269fd0 -short}){.echo ***;!do poi(${obj}+0x4);!do poi(${obj}+0x8);!objsize ${obj}}
    ***
    String: somestring
    String: this is a string i stored in session scope
    sizeof(0x626a81c) =      160 (    0xa0) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
    ***
    String: alargeintarray
    Name: System.Int32[]
    MethodTable 0x01b226b0
    EEClass 0x01b22638
    Size 8000012(0x7a120c) bytes
    GC Generation: 3
    Array: Rank 1, Type System.Int32
    Element Type: System.Int32
    Content: 2,000,000 items
    sizeof(0x626a82c) = 8,000,076 (0x7a124c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
    ***
    String: sometimesbig
    Name: System.Object[]
    MethodTable 0x01b2209c
    EEClass 0x01b22018
    Size 4000016(0x3d0910) bytes
    GC Generation: 3
    Array: Rank 1, Type CLASS
    Element Type: System.Object
    Content: 1,000,000 items
    sizeof(0x626a83c) = 4,000,076 (0x3d094c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
    到这里,我们看到,一个很小的字符串:this is a string i stored in session scope。在Session的 somestring里面。然后是在Session的alargeintarray有一个8M的Int32数组,最后,是一个4M的sometimesbig在session的sometimesbig。
    下面如何解决?就看我们的业务逻辑和代码实现啦!和偶无关了,嗬嗬!

    发表于 @ 0:23 | (阅读:5698;评论:21)

    2006年12月21日

    (序 & 跋)

       此文及后面的系列,都是从tess老大那里翻译过来的。一直和GTEC的老牛们作CASE(此句有误,一直提CASE,等老牛们提供答案),算是粘到了一点仙气。偶一直比较懒,所以以前精心抄袭的文章,今天再次精心作序于此,希望对各位挣扎于现实与理想的各位,共享,共勉。tess老大的文章国内似乎有人翻译过,但偶个人观点,不看好,因为好东西都没了哦。

       不是我不舍得,意思是,我的post里面基本上没有link,但是从google上都能搞到,如tess老大的系列debug文章。写程序的人,用好google应该是第一要素啊,哇哈哈哈!(百度除外,偶鄙视的公司)

       每个post的题目都挺吓人,但是最终发现问题以及解决掉的方法,都异常简单。简单,产生丑陋。

     

    问题描述:

    程序慢的要死,CPU占用始终持续在70%-80%之间

    解决步骤:

    性能监视器。对于高CPU占用,一般的是这三个原因:

    ·         高的离谱的循环

    ·         太多的加载(比如,许多小的对象被频繁的处理)

    ·         GC作了太多的事情

    第一种情况,当你在恰当的时机抓到一个dump,就非常容易解决,一般而言,都是因为业务处理逻辑造成的。第二种情况,一般需要从硬件上考虑,scale up或者scale out,都行。

    是否是GC的问题,我们需要看性能监视器里面的.NET CLR Memory计数器。这里面,最重要的是.net CLR Memory / % Time in GC. 这个值的阀值,可能是5%或者30%或者20%。实际上,没有一个准确的阀值存在的。当然,这个数字理论上应该接近于0%才对。

    在GC里面,导致高CPU占用的原因,通常是因为过高的分配速率(对应到性能监视器里面的.net CLR Memory / allocated bytes/sec计数器。但实际上,如果所有的GC操作都在第0代上,则不会导致这个问题。真正的元凶,是大量的2代操作。如N多的对象在被移动到2代或者从2代中被释放。另一个原因就是我们熟知的大对象操作(LOH)

    再强调一次,没有什么准确的指标,就告诉我们,超过了它就是出问题了,这是不可能的。包括微软给我们的大多数Practics Training,只有“尽量”、“尽可能”、“如果”等,而不是“一定”、“必须”。

    如同我在那篇“浅谈GC中”讲到的一样,如果你搞了GC.Collect(2)或者GC.GetTotalMemory(true),那么也会导致大量的2代回收。

    对于这个问题,我从性能监视器中抓到了这些数据:

    % Time in GC                        ~40 % average
    allocated bytes / sec              400 MB average
    # Induced GC                        0
    # Gen 0 Collections                28.379
    # Gen 1 Collections                28.378
    # Gen 2 Collections                28.378

    看第二行,真是让人晕倒!每秒分配400M字节!但实际上,我的代码中没有分配任何这么大的东西,有点太离谱了吧?!如果看最后面三行,也比较搞笑,0、1、2代的分配几乎完全相同。这实际上说明有LOH在压缩,或者有大量的对象冲进了第2代,然后又被立刻释放掉。

    开始debug吧!

    GC问题很难debug,因为:

    1. 如果在GC中间过程中用adplus -hang模式抓了一个dump,基本上从dump里面看不到任何高CPU占用的原因。
    2. 即时你通过性能监视器找到了GC的问题,也抓到了dump,但是很难分析。

    实际上,通用的做法是每隔一小段时间,你就抓一次dump。如果看起来都差不多,那么有可能就能分析出哪里的问题了。

    步骤1 - 我们在GC里面不?

    如果我们的OS是server,那么每个CPU有一个GC线程(如果超线程的话,就是2个)。如果是非server的,那么只有一个GC线程。我们正在看到的这个dump,是在一个双核的、带有.NET2.0的机器上产生的。

    如果我们不在GC过程中,那么我们会有两个线程(每个CPU一个):

      18  Id: 134c.918 Suspend: 1 Teb: 7ffa8000 Unfrozen
    ChildEBP RetAddr  Args to Child             
    01e1fe68 7c822124 77e6baa8 00000398 00000000 ntdll!KiFastSystemCallRet
    01e1fe6c 77e6baa8 00000398 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
    01e1fedc 79e77fd1 00000398 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
    01e1ff20 79e77f9a 00000398 ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
    01e1ff70 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
    01e1ff80 79f3549b ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
    01e1ffa8 79f6ece3 00000000 b28c067c 01e1ffec mscorwks!SVR::gc_heap::gc_thread_function+0x2e
    01e1ffb8 77e66063 000e7660 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
    01e1ffec 00000000 79f6ec79 000e7660 00000000 kernel32!BaseThreadStart+0x34

    上面的代码表明,GC正在等着干活……在我抓到的dump中,GC看起来这个样子:

    16  Id: f28.1150 Suspend: 1 Teb: fff82000 Unfrozen
    ChildEBP RetAddr  Args to Child             
    0248fd28 7d4d8c46 000002dc 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15
    0248fd98 79e77fd1 000002dc ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac
    0248fddc 79e77f9a 000002dc ffffffff 00000000 mscorwks!PEImage::LoadImage+0x199
    0248fe2c 79e77f50 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
    0248fe3c 79f35e1a ffffffff 00000000 00000000 mscorwks!CLREvent::Wait+0x17
    0248fe54 7a0d5a3b 001afbf0 0000000d 00000002 mscorwks!SVR::t_join::join+0x61
    0248ff54 79f391bf 00000002 001afbf0 00000000 mscorwks!SVR::gc_heap::plan_phase+0xd78
    0248ff70 79f39954 00000002 ffffffff 001afbf0 mscorwks!SVR::gc_heap::gc1+0x57
    0248ff88 79f35422 00000000 00000000 001afbf0 mscorwks!SVR::gc_heap::garbage_collect+0x37c
    0248ffa8 79f6ece3 00000000 914b1904 0248ffec mscorwks!SVR::gc_heap::gc_thread_function+0x68
    0248ffb8 7d4e0729 001afbf0 00000000 00000000 mscorwks!SVR::gc_heap::gc_thread_stub+0x9b
    0248ffec 00000000 79f6ec79 001afbf0 00000000 kernel32!BaseThreadStart+0x34

    看上面的粗体,哦,正在Collect。就是说,我们闯到了GC的肚子里面。

    步骤2 - GC为什么开始工作了?

    先看一下,CPU都被谁用掉了?

    0:029> !runaway
     User Mode Time
      Thread       Time
     
     14:11b8      0 days 0:00:55.687
      16:1150      0 days 0:00:45.500
      17:7a8       0 days 0:00:43.875
      21:1244      0 days 0:00:23.140
     
      0:ea0       0 days 0:00:00.046
      29:fc8       0 days 0:00:00.000
      28:11f0      0 days 0:00:00.000

    0:021> .time
    Debug session time: Tue Jun 20 09:46:04.000 2006 (GMT+2)
    System Uptime: 4 days 6:36:51.031
    Process Uptime: 0 days 0:16:53.000
      Kernel time: 0 days 0:00:45.000
      User time: 0 days 0:02:48.000

    上面来看,大约有17分钟被程序用掉了。我们看一下14号在作什么?

      14  Id: f28.11b8 Suspend: 1 Teb: fff88000 Unfrozen
    ChildEBP RetAddr  Args to Child             
    020afc8c 7d4d8c46 00000224 00000000 020afcd0 ntdll!ZwWaitForSingleObject+0x15
    020afcfc 79e77fd1 00000224 00009c40 00000000 kernel32!WaitForSingleObjectEx+0xac
    020afd40 79e77f9a 00000224 00009c40 00000000 mscorwks!PEImage::LoadImage+0x199
    020afd90 79e77f50 00009c40 00000000 00000000 mscorwks!CLREvent::WaitEx+0x117
    020afda0 79f5b69c 00009c40 00000000 00000000 mscorwks!CLREvent::Wait+0x17
    020afe20 7a1121c3 001862f8 00009c40 00000000 mscorwks!ThreadpoolMgr::SafeWait+0x73
    020afe94 79f71123 00000000 00000000 00000000 mscorwks!ThreadpoolMgr::WorkerThreadStart+0xf1
    020affb8 7d4e0729 0019cab8 00000000 00000000 mscorwks!ThreadpoolMgr::intermediateThreadProc+0x49
    020affec 00000000 79f710dd 0019cab8 00000000 kernel32!BaseThreadStart+0x34

    哦,很清闲,什么都没干。但是,那17分钟中,有56秒在用着CPU,什么意思呢???我们继续看一下21号线程的托管堆。

    0:021> !clrstack
    OS Thread Id: 0x1244 (21)
    ESP       EIP    
    029ef1e8 7d61c824 [HelperMethodFrame: 029ef1e8]
    029ef254 02920df3 LargeObjectHeap.GetLotsOfDatesXML(Int32)
    029ef2a4 02920b20 LargeObjectHeap.Button1_Click(System.Object, System.EventArgs)
    029ef2b4 6881bdc6 System.Web.UI.WebControls.Button.OnClick(System.EventArgs)
    029ef2c8 6881bfbc System.Web.UI.WebControls.Button.RaisePostBackEvent(System.String)
    029ef2dc 6881bf38 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(System.String)
    029ef2e0 687d91e0 System.Web.UI.Page.RaisePostBackEvent(System.Web.UI.IPostBackEventHandler, System.String)
    029ef2e8 687d912a System.Web.UI.Page.RaisePostBackEvent(System.Collections.Specialized.NameValueCollection)
    029ef2f8 687dcbbf System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
    029ef4b0 687db521 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
    029ef4e0 687db487 System.Web.UI.Page.ProcessRequest()
    029ef518 687db3a7 System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
    029ef520 687db33a System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
    029ef534 02920795 ASP.largeobjectheap_aspx.ProcessRequest(System.Web.HttpContext)
    029ef538 686888df System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
    029ef56c 6865a071 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
    029ef5ac 6865a39b System.Web.HttpApplication.ResumeSteps(System.Exception)
    029ef5f4 6865912d System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
    029ef610 6865e0bd System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
    029ef644 6865dd72 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
    029ef650 6865c447 System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
    029ef800 79f1ef33 [ContextTransitionFrame: 029ef800]
    029ef850 79f1ef33 [GCFrame: 029ef850]
    029ef9a8 79f1ef33 [ComMethodFrame: 029ef9a8]

    看上面的call stack,我们看到了LargeObjectHeap.GetLotsOfDatesXML(),这个咚咚触发了LOH的回收,然后按次序触发了2代、1代、0代。

    但是只有一个大对象,并不会导致高CPU的占用啊!那我们看一下实际的代码吧!

        String GetLotsOfDatesXML(int i)

        {

            String dates = "<Dates>";

            for (int j = 0; j < i; j++)

            {

                DateTime dt = DateTime.Now.Add(new TimeSpan(j, 0, 0, 0));

                dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

                dates += "<Date><Month>" + dt.Month + "</Month><Day>" + dt.Day + "</Day><DayOfWeek>" + dt.DayOfWeek + "</DayOfWeek><Date>";

            }

            dates += "</Dates>";

            return dates;

        }

    如果外面传来的i小点还好,但是如果很大呢?对于确切地i是多少,我们需要一点一点地开始找。从callstack上面,我们看到是LargeObjectHeap.Button1_Click调用了GetLotsOfDatesXML方法。

        protected void Button1_Click(object sender, EventArgs e)

        {

            String str = GetLotsOfDatesXML(Int32.Parse(txtNumIterations.Text));

        }

    在这里,txtNumIterations是一个textbox控件。下面,基本上都是!do和!dso的工作了。

    0:021> !dso
    OS Thread Id: 0x1244 (21)
    ESP/REG  Object   Name
    029ef0f0 0b1a2270 System.String    <String is invalid or too large to print>
    029ef1a8 0b1a2270 System.String    <String is invalid or too large to print>
    029ef1bc 0b1a2270 System.String    <String is invalid or too large to print>
    029ef228 06a759e4 System.String    </DayOfWeek><Date>
    029ef28c 06a76df0 ASP.largeobjectheap_aspx
    029ef2b4 06a77b84 System.ComponentModel.EventHandlerList
    029ef2bc 06a77a84 System.Web.UI.WebControls.Button
    029ef2c8 06a76df0 ASP.largeobjectheap_aspx
    029ef304 06a76880 System.Web.HttpContext
    ...

    0:021> !do 06a76df0
    Name: ASP.largeobjectheap_aspx
    MethodTable: 02746ccc
    EEClass: 028d24cc
    Size: 380(0x17c) bytes
     (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\debuggersamples\e7443224\5232f845\App_Web_h0ctkxwz.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    790fa3e0  4001fe0        4        System.String  0 instance 02a7cba4 _id
    790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
    68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 00000000 _parent
    68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
    68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 00000000 _viewState
    68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 00000000 _namingContainer
    68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
    ...
    68a7d910  4000004      16c ...ebControls.Button  0 instance 06a77a84 Button1
    68a95f40  4000005      170 ...bControls.TextBox  0 instance 06a77be4 txtNumIterations
    68a2bc80  4000006      174 ...Controls.HtmlForm  0 instance 06a77688 form1

    好,继续看一下这个textbox。

    0:021> !do 06a77be4
    Name: System.Web.UI.WebControls.TextBox
    MethodTable: 68a95f40
    EEClass: 68a95ebc
    Size: 80(0x50) bytes
     (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    790fa3e0  4001fe0        4        System.String  0 instance 02a85a04 _id
    790fa3e0  4001fe1        8        System.String  0 instance 00000000 _cachedUniqueID
    68a2af44  4001fe2        c ...em.Web.UI.Control  0 instance 06a77688 _parent
    68a91070  4001fe3       2c         System.Int32  0 instance        5 _controlState
    68a85ea0  4001fe4       10 ...m.Web.UI.StateBag  0 instance 06a788f0 _viewState
    68a2af44  4001fe5       14 ...em.Web.UI.Control  0 instance 06a76df0 _namingContainer
    68a273d0  4001fe6       18   System.Web.UI.Page  0 instance 06a76df0 _page
    68a92e2c  4001fe7       1c ...+OccasionalFields  0 instance 06a78974 _occasionalFields
    68a2b378  4001fe8       20 ...I.TemplateControl  0 instance 00000000 _templateControl
    ...

    0:021> !do 06a788f0
    Name: System.Web.UI.StateBag
    MethodTable: 68a85ea0
    EEClass: 68a85e30
    Size: 16(0x10) bytes
     (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    79113dfc  400235f        4 ...tions.IDictionary  0 instance 06a78900 bag
    79104f64  4002360        8       System.Boolean  0 instance        1 marked
    79104f64  4002361        9       System.Boolean  0 instance        0 ignoreCase

    0:021> !do 06a78900
    Name: System.Collections.Specialized.HybridDictionary
    MethodTable: 7a747ad4
    EEClass: 7a7aa890
    Size: 20(0x14) bytes
     (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    7a747bac  4001145        4 ...ed.ListDictionary  0 instance 06a78924 list
    790fea70  4001146        8 ...ections.Hashtable  0 instance 00000000 hashtable
    79104f64  4001147        c       System.Boolean  0 instance        0 caseInsensitive

    0:021> !do 06a78924
    Name: System.Collections.Specialized.ListDictionary
    MethodTable: 7a747bac
    EEClass: 7a7aa918
    Size: 28(0x1c) bytes
     (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    7a747c78  4001148        4 ...ry+DictionaryNode  0 instance 06a78940 head
    790fed1c  4001149       10         System.Int32  0 instance        1 version
    790fed1c  400114a       14         System.Int32  0 instance        1 count
    791117c8  400114b        8 ...ections.IComparer  0 instance 00000000 comparer
    790f9c18  400114c        c        System.Object  0 instance 00000000 _syncRoot

    0:021> !do 06a78940
    Name: System.Collections.Specialized.ListDictionary+DictionaryNode
    MethodTable: 7a747c78
    EEClass: 7a7aa9b8
    Size: 20(0x14) bytes
     (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    790f9c18  4001158        4        System.Object  0 instance 06a66bd8 key
    790f9c18  4001159        8        System.Object  0 instance 06a78914 value
    7a747c78  400115a        c ...ry+DictionaryNode  0 instance 00000000 next

    0:021> !do 06a66bd8
    Name: System.String
    MethodTable: 790fa3e0
    EEClass: 790fa340
    Size: 26(0x1a) bytes
     (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String: Text
    ...

    找到了Text属性,看看是多少?

    0:021> !do 06a78914
    Name: System.Web.UI.StateItem
    MethodTable: 68a131b4
    EEClass: 68a13144
    Size: 16(0x10) bytes
     (C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    790f9c18  4002362        4        System.Object  0 instance 06a78184 value
    79104f64  4002363        8       System.Boolean  0 instance        1 isDirty

    0:021> !do 06a78184
    Name: System.String
    MethodTable: 790fa3e0
    EEClass: 790fa340
    Size: 28(0x1c) bytes
     (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String: 40000
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    790fed1c  4000096        4         System.Int32  0 instance        6 m_arrayLength
    790fed1c  4000097        8         System.Int32  0 instance        5 m_stringLength
    790fbefc  4000098        c          System.Char  0 instance       34 m_firstChar
    790fa3e0  4000099       10        System.String  0   shared   static Empty
        >> Domain:Value  001a8868:790d6584 001ca990:790d6584 <<
    79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars
        >> Domain:Value  001a8868:06a303f0 001ca990:06a34118 <<


    靠,循环了4万次!

    结论:

    地球人都知道,用+=会产生3*n个对象,如果用StringBuilder,就不会有这个问题了。

    发表于 @ 23:38 | (阅读:5034;评论:16)

    2006年12月18日

     
  • 相关阅读:
    Fast data loading from files to R
    php的循环与引用的一个坑
    让我安静的写会儿代码
    chrome一个奇怪的问题
    用原生js给DOM元素添加、删除一个类名
    在ie浏览器下背景图片不显示的解决办法
    伪元素选择器之 ::placeholder
    基础版放大镜--面向对象
    元素尺寸大全
    如何解决PC端和移动端自适应问题?
  • 原文地址:https://www.cnblogs.com/fuyingke/p/776663.html
Copyright © 2020-2023  润新知