究竟是什么导致了进程崩溃?

原文出自:http://blogs.msdn.com/tess/archive/2005/11/28/497386.aspx

版权归原文作者所有,转载请注明出处

究竟是什么导致了进程崩溃?


不知道为什么,事件查看器中有数以千计的w3wp.exe意外停止的日志记录,或者进程以某种怪异而不明确的方式退出.

当进程崩溃或者一个叫做EPR(Exit PRocess)的特殊事件被触发,可以使用调试器如windbg附加到进程,等待EPR被抛出然后进行内存转储.安装the debugging tools for windows后,会得到一个叫做adplus的vbs脚本,它会自动帮你做上述工作并且打印出转储操作中发生的大部分异常.

调试技巧:打开crash模式下得到的内存转储,调试器将自动定位到崩溃发生时的活动线程(也是最有嫌疑的线程).要在故障线程和其他线程间切换,输入 "~" 来列出所有线程,故障线程用"."来标记.

如果转储仅显示一个活动线程且这个线程是进程的主线程,这个进程很可能被从外部中止(系统监视器,系统可用内存低,使用iisreset等)

稍后的日志中我将会对一些场景作深入讨论,但我是一个思维严谨的人,希望从一些常见的情况开始讨论,这样当发生托管进程退出的时候,你就知道该查看转储的哪些部分.


下面是常见的导致进程崩溃的原因:


堆栈溢出异常

分配给线程堆栈的内存耗尽时就会发生内存溢出异常.默认情况下是1MB,调用栈可以非常深,所以,发生这个异常基本是因为无限递归,也就是函数A调用函数B,函数B反复调用自身,无法终止.

不幸的是,误用错误处理程序块经常导致隐晦的无限递归.想象以下的场景:程序发生异常,异常处理程序处理然后将其记录到日志文件.由于某种原因(比如拒绝访问),记录的时候再次发生异常,此时再次利用异常处理来处理刚发生的异常.处理一个,抛出另一个,无限递归循环... 故事的寓意?不要用异常处理程序来处理异常处理过程中发生的异常 :)

调试技巧:运行"kb 2000"(查看那本地栈)和"!clrstack"(sos.dll提供,查看托管栈)


内存溢出异常

大多数情况下内存溢出是由设计问题导致的,要么是cache或者session使用了过多的内存.使用正确的话,cache能带来极大的性能提升,也就是说,缓存那些访问最频繁的数据,不需要时尽快移除.在旧时代的asp保存对象到session不大会出现问题,因为开发人员仅保存最需要的条目.和减少并发用户数来解决内存占用过高一样,保存大dataset到session,都是效率杀手,你可能在耗时的垃圾搜集和检索cache与需要的时候再数据库检索数据的比较上做过头了.


没有特定的标准来决定什么时候该保存东西到session/cache,什么时候不该.最好在早期决定有多少用户需要处理,在此基础上决定每个用户允许保存多少多少数据.然后以多于最大可处理用户的数值做压力测试,以此确定能处理多少.更好的是,在不使用session的情况下来查看性能.区别在于不同的用户数量.

生产环境下的内存问题非常难于修正,因为通常需要重新设计,早期多付出一点可以避免后期付出更多.

调试技巧:运行!dumpheap -type System.Web.Caching.Cache来获取缓存的根,然后运行!objsize 根地址 来得知缓存了多少数据.(注意:InProc 模式下的session也保存在cache中

)


关于Out of Memory更深入的讨论,请查看我之前的日志


COM组件中未处理的异常

要是应用程序调用了本地COM组件,如果发生了未处理的异常会导致崩溃.举个例子:引用了已经释放的内存或相似的问题.

调试技巧:kb 2000 可以展示堆栈上的com组件,这样可以缩小范围.


本地堆冲突

和GC漏洞一样,本地堆冲突排错是最恶心的.写入了错误内存地址就会发生本地堆冲突.随之而来的问题在于:写入错误的内存地址不会发生任何错误,而当其他程序用正确方式访问这个地址的时候才会出错.换句话说,有贼偷取了内存数据.写入的内存地址可能在堆上,更恶劣的情况是:这个地址是用来保存指令的(指令将被改写)或者是堆栈,以至于代码调用到一半就无处可去.这个在写入buffer越界或者同样效果的操作时,发生的最频繁.

关于堆冲突的介绍请看Geoff Gray的文章.当由于堆冲突崩溃时,故障栈通常位于ntdll的堆分配函数,解决这个问题,需要使用GFlags和PageHeap抓到这个"贼".然而,为什么难于抓到这个"贼",原因在于:这个问题出现的非常随机并且非常难以重现.


托管堆冲突

托管堆冲突也是发生在托管堆上的堆冲突.同样的,在发生后很久才会显现出来.托管堆冲突是由于托管堆某一块被强行覆盖导致的.一般而言托管代码中,缓冲区是不会溢出的。如

果对一个byte[]越界写入,会报IndexOutOfRange 一类的异常.导致托管堆冲突最常见的原因是:代码调用用PInvoke传递一个过大的buffer进来。PInvoke函数越界写入buffer并覆

盖了紧挨着buffer的对象使用的内存。稍后垃圾搜集器开始遍历托管堆,问题就出来了,进程崩溃。
如果在当前活动栈的顶部有GC函数,就需要在代码中寻找是否有传递了buffer的PInvokes调用。


致命的执行引擎异常

这种异常非常少见,如果出现了,一般都是程序bug.一般是由于执行了一段CLR认为不应该执行的代码,因为无法从该点恢复,所以CLR抛出致命的执行引擎异常并结束。在事件日

志中会记录准确的地址。要是遇到了这种情况,最好联系技术支持,可能的话附上崩溃时的内存转储文件,它对问题的解决有极大帮助。

GC空洞

这也非常少见。CLR的非托管部分引用托管内存并“忘记”告诉GC这部分被引用,GC不知道应该保留被引用的内容(如果这部分没有被其他对象引用)而进行了回收,那么这个指针可

能指向意一个地址,最终导致严重问题.Yun Jin讨论了这个问题,地址:http://blogs.msdn.com/yunjin/archive/2004/02/08/69906.aspx

进程可用内存将为0

如果在进程死亡的同时从性能监视器观察到 物理内存/可用数(Mb) 大幅下降至0,这能导致进程崩溃。下面的任务就是找出原因

外部进程 杀死/回收 了asp.net进程

有无数次调试的时候,进程被外部程序杀死而不是进程自己崩溃的经历。一般来说,这种情况一般不是进程崩溃的真正原因。但在调试过程中,最好检查一下事件日志确保没有外部因素(如iisreset)杀死进程。

健康监视设置

健康监视设置也会导致进程回首,通常会设置每24小时或者服务器空闲20分钟回收进程。在开始调试之前最好检查一下应用程序池或者machine.config中的健康监视设置,不要因为这些设置误导调试工作.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值