到底是什么原因导致我的进程崩溃?

当你你有1000个w3wp.exe文件在eventviewer中意外停止,或者您的进程以某种奇怪的未定义方式退出,您不知道原因。
当一个进程崩溃或退出时,将触发一个称为EPR(Exit process)的特殊事件,因此使用类似于windbg.exe文件我们可以附加到进程中,等待epr被抛出,然后进行内存转储。安装windows调试工具时,会得到一个名为adplus的vbs脚本,它将为您自动执行此操作,并打印进程生命周期中发生的大多数异常的日志。
调试提示:当您在-crash模式下打开一个转储时,您将自动定位到崩溃发生时处于活动状态的线程(最有可能是可疑的线程)。如果您切换线程并想返回出错线程,请键入~列出所有线程,错误线程将被标记为一个点。
如果dump只显示进程中的一个活动线程,并且该线程是主线程,则该进程可能被外部的东西(运行状况监视、低系统内存、iisreset等)终止

不分先后顺序,以下是我们看到的支持率最高的一些:

Stack Overflow Exceptions

当为线程的堆栈分配的内存用完时,将发生堆栈溢出异常。默认情况下,它是1 MB,所以你的调用堆栈可能很深,所以大多数情况下发生这种情况是因为无限递归,也就是说,function调用FunctionB,后者再次调用FunctionB,后者再次调用FunctionB。。。没有停止条件。
不幸的是,异常处理应用程序块的错误使用是一个相当常见的模糊的无限递归情况。想象一下这个场景:你的应用程序得到一个异常,异常处理程序启动,你已经将它设置为记录到一个文件中。在记录日志时,您会得到某种类型的异常(比如访问被拒绝),并且您已经设置了异常处理程序来处理此异常。在这种情况下,您将在一个无限递归循环中处理一个异常,抛出另一个异常,处理它,抛出另一个。。。你明白要点了。这个故事的寓意是什么?不要在异常处理程序中使用异常处理程序来处理异常。
如果您运行“kb2000”(查看本机堆栈)和“!clrstack”(从sos.dll要查看托管堆栈),您可以找到递归模式以跟踪递归发生的位置/原因。

Out Of Memory Exceptions

大多数情况下,发生内存不足异常是由设计问题引起的,在设计问题中,缓存或会话作用域中存储的内存过多。如果以正确的方式使用缓存,那么缓存对于提高性能是非常有用的,也就是说,缓存的数据最多,而且缓存的时间不会超过需要的时间。在旧的ASP中,如果将对象存储在session范围内,就会出现问题,相信我,这是一种伪装,因为开发人员只在session范围内存储了最必要的项。例如,在会话范围内存储大型数据集通常会适得其反,因为您减少了网站可以处理的并发用户数,而且当内存足够大时,在缓存中进行垃圾收集和搜索所需的时间可能比从数据库中请求数据的开销要多真的需要它。
在何时应该在会话/缓存中存储内容以及何时不应该存储时,这里没有一刀切的解决方案。最好的做法是在早期阶段,确定应用程序需要能够处理的用户数,并在此基础上确定每个用户可以允许的存储量。然后对超过最大用户数的用户进行压力测试,以确保你能应付。最好是对处于会话状态的对象进行压力测试,看看性能如何。不同的用户数量不同。
在生产过程中,内存问题是很难解决的,因为它们通常需要大量的重新设计,所以在早期阶段花费一分钱可以节省很多钱。
调试提示:运行!dumpheap -type System.Web.Caching.Cache获取缓存根,然后对这些地址进行!do objsize,以了解您在缓存中为不同的应用程序存储了多少。(注意:InProc会话状态也存储在缓存中)

Unhandled exception in COM Component

如果应用程序调用本机COM组件,如果其中出现未经处理的异常,则可能会崩溃。例如,如果您引用的是已释放的内存或类似内存。kb2000将在堆栈上显示COM组件,这样您就可以缩小它的范围。
 
Native heap corruption
 
 
这与GC漏洞一起是最棘手的问题。当有人写入不应该写入的位置时,本机堆损坏。这样做的问题是,当写入错误地址的代码执行时,您不会看到错误,而是当其他人试图以正确的方式访问该内存地址时。换句话说,在“小偷”出现很久之后。写入的位置可以是堆,或者更糟的是,一个存储代码的位置(这样指令就会被覆盖),或者是堆栈,这样代码就可以调用到不知道的地方。最常见的情况是,当您在缓冲区或其他类似的边界之外写入时,就会发生这种情况。
当您因为堆损坏而崩溃时,出错的堆栈通常位于ntdll中的堆分配函数中,要解决此问题,您需要使用GFlags或PageHeap运行,以便能够当场抓到小偷。然而,之所以如此难以捕捉,是因为这些问题非常随机,很难重现。
 
Managed Heap Corruption
 
托管堆损坏是发生在托管堆上的堆损坏。同样,在这里,你会在问题发生很久之后才发现问题。如果有人重写不允许的托管堆的一部分,则会发生托管堆损坏。通常不能在托管代码中缓冲溢出。如果您有一个byte[]并且试图在边界外写入,那么您将得到一个indexautofrange异常或类似的异常。托管堆损坏的最常见原因是,名为PInvoke的代码在某种类型的缓冲区中传递,但缓冲区太小。PInvoked函数写入缓冲区,但写入的内容超出缓冲区的范围,并写入托管堆上的下一个对象。稍后,当垃圾回收器执行其工作时,它会尝试遍历堆,但情况会变糟,因此进程会崩溃。
如果发生崩溃,并且活动堆栈的顶部包含GC函数,则开始在代码中查找pinvoke,看看是否传递的缓冲区太小。
 
Fatal Execution Engine Exceptions
 
致命的执行引擎异常相当罕见,但当它们发生时,通常是一个bug。这意味着,由于某种原因,我们进入了一些在CLR中不应该出现的代码,CLR决定,如果有人出现在这里,让我们抛出一个致命的执行引擎异常并死亡,因为我们无法从这一点恢复过来。在事件日志中,这将被记录为执行引擎异常发生,并且列出的地址将准确地告诉代码中它发生的位置。如果您找到其中一个,但找不到有关它的知识库文章,请联系支持人员,最好提供崩溃转储,因为这将大大加快解决问题的时间。
 
GC Holes
 
这些也是相当罕见的。CLR的非托管部分有一个指向托管内存的指针,并且“忘记”告诉GC有关它的信息,因此GC不知道保留它(如果没有其他根)或跟踪它的移动,这意味着如果GC在“错误的时间”发生,指针可能会指向任何地方,从而造成很大的破坏。

No available memory left

如果你的进程死了,同时你看到内存/available兆字节的大幅度下降,达到0,这可能会导致进程崩溃。当然,这里的任务是检查是什么进程偷走了内存。

External process kills/Recycles the process

无数次,当我有一个客户在崩溃模式下连接adplus时,我会发现内存转储,进程被外部的东西杀死了。我想添加这个问题,因为通常当你对崩溃进行故障排除时,这并不是你要排除的崩溃的真正原因(只有在极少数情况下),而是有人运行iisreset或者在不知道你有一个调试程序等待崩溃的情况下杀死了进程。所以如果你正在排除一个崩溃并得到一个内存转储。检查事件日志以确保没有人运行iisreset,因此这是导致崩溃转储的原因。这样可以节省你几个小时去寻找车祸的罪魁祸首。
另一个奇怪的地方是安装了某种监视软件来监视服务器是否按预期服务页面,如果没有,则关闭进程。但通常这也会在eventviewer中记录发生的事件。

Health monitoring settings

当然,我知道我们设置为每24小时循环一次进程,或者如果服务器空闲超过20分钟,但无论如何值得一提的是,因为我们经常遇到这些问题。同样,这里,通常会在事件日志中记录一个事件,说明进程被回收的原因,但故事的寓意是在iis或中简要查看应用程序池的运行状况监视设置计算机配置看看你打开了什么回收意见,这样你以后就不会有惊喜了。
发生崩溃的原因比上面提到的要多,但它们通常都是相当模糊的,所以我希望这篇文章能让你对如何开始寻找进程突然失效有一点了解。

posted on 2020-08-27 13:58  活着的虫子  阅读(1613)  评论(0编辑  收藏  举报

导航