在国庆假期的最后一天晚上,我接到了部门领导的电话,告知某省的服务即将崩溃,需要紧急修复。这个问题再次涉及到我们所谓的“远古项目”,由于同事们都在休假,无法远程协助,我不得不打车前往公司。远程连接到服务器后,查看日志发现抛出的堆栈异常信息中包含了“outofmemoryexception”,错误源自a.dll文件。
这个“远古项目”的大致情况如下:
(1)根据日志定位问题
日志中的错误信息虽然有限,但还是提供了关键线索,帮助我们确定问题所在。我们直接查找A类库的源码,发现相关代码大约有1000行,具体哪行代码导致问题并不清楚。由于无法用大量设备进行模拟测试,情况变得更加复杂。随后,我通过Google搜索“OutOfMemoryException”异常,发现这可能是由于线程方面的内存溢出问题。缩小了代码检查范围后,我开始搜索代码中对Thread对象的使用。经过一番查找,我发现了以下代码段:
//...代码上下文
byte[] sendbytes = new byte[] { xxx };
var thread = new Thread((_) => {
Send(sendbytes);
});
thread.Start();
//...代码上下文这段代码在每小时数据采集时,会集中在一个时间段内频繁创建线程进行数据发送,显然这是导致异常的一个主要原因。
(2)根据问题代码继续分析
在程序开发中,创建线程的成本很高,尤其是在短时间内频繁创建线程。这样的代码显然存在问题,极有可能导致内存溢出。我回想起之前读过的一本书《.NET性能分析》,书中提到“线程栈通常很小,Windows上默认最大为1MB,大多数线程只使用很少的栈页。”这进一步增加了这段代码可能导致崩溃的可能性。
发现了可能导致异常的代码后,如何解决呢?我想到的解决方案是利用生产者-消费者模式,建立一个发送队列,并启动一个常驻的发送线程逐步发送数据。然而,“远古项目”的代码结构过于混乱,牵一发而动全身,即使是这种简单的思路也难以实现。此外,由于框架版本较低,无法使用一些新的语法特性。
在这种情况下,我决定使用ThreadPool对象来解决问题,因为它能够满足以下需求:
我将代码修改为:
byte[] sendbytes = new byte[] { 0 };
ThreadPool.QueueUserWorkItem(_ => {
Send(sendbytes);
});尽管进行了修改,我对解决问题的效果仍不确定,因为根据线程池的原理,如果任务量过大,仍然会创建新的线程。但由于线程池对线程管理较好,我决定提交代码并重新部署到服务器上进行测试。
为了验证这一解决方案,我进行了以下测试:
//模拟在同一个时间点内大量开启线程模拟多设备发送数据
for (int i = 0; i < 1000; i++)
{
ThreadPool.QueueUserWorkItem(_ => {
//模拟发送数据耗时
Thread.Sleep(1000);
});
}通过观察Visual Studio的内存监测变化,我发现使用线程池后,CPU和内存占用几乎没有波动。这让我对微软的线程池实现感到非常满意。由于时间紧迫,我只能先部署这个版本到服务器上,观察其效果。

如果问题再次出现,我计划采取以下措施进行进一步排查:
整个过程耗时约3小时,部署新版本后观察了一个多星期,没有再次出现崩溃异常。尽管如此,我意识到对问题的根本原因仍需进一步研究。
以上就是OutOfMemoryException异常解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号