当我使用Socket.IO时,为什么我收到错误类型’System.OutOfMemoryException’的未处理exception

我编写了一个程序来获取屏幕截图并发送到服务器。 每次,我都有截图并转入base64,然后使用Socket.IO发送。 (使用SocketIOClient.dll)

Dictionary image = new Dictionary(); image.add("image", ""); private void windowMonitorTimer_Tick(object sender, EventArgs e) { image["image"] = windowMonitorManager.MonitorScreen(); client.getSocket().Emit("Shot", image); } 

windowMonitorManager.MonitorScreen()用于返回base64字符串。 如果我不使用client.getSocket().Emit("Shot", image) ,程序可以运行正确,但如果我添加这一行,程序停止2秒(发送近80次)并给我错误:

 An unhandled exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll 

如果我不发送字符串只要这个,只是一个短字符串“hello”,它发送1600次然后发生同样的问题。

有人知道如何调试这个问题?

////////////////////////////////////////////////// ////////////////////////////////////////////////// ////////////////////////////////////////////////// //////////////////////

我尝试测试socket.Emit(),并发现它有其限制。

例如,我发送一个10000000的字符串,经过88次,就会发生内存不足的问题。 如果我发送一个5000000的字符串,经过170次,就会出现同样的问题。

当进程消耗比默认系统允许的内存大得多的内存(如32位系统上的2 GB)时,内存不足主要是exception抛出,在64位系统上,它更高,但仍受某些特定的约束。实际限制,它不是2 ^ 64的理论值,它不同于操作系统和OS,也依赖于底层RAM,但对于单个进程来说足够大,现在这种情况可能由于多种原因而发生:

  • 内存泄漏(最突出),主要与非托管代码调用相关联,如果有一个未解除分配或释放的句柄或内存分配,在一段时间内会导致进程的大量内存分配,从而导致exception,当系统无法再映射时。

  • 托管代码可能会泄漏而且我已经这样做了,当对象被连续创建并且它们没有被引用时,即它们仍然可以在GC上下文中访问,所以你可以导致这种情况,我已经在我的代码中完成了这个:)

  • 这不是空引用或损坏,因此在这种情况下采用直接堆栈跟踪几乎没有用,只是因为每次都可能得到不同的堆栈,它就像进程线程的堆栈,当exception发生时这将主要是误导,所以不要这样做。 执行线程的方法并不意味着它导致内存泄漏,并且对于不同的线程它将是不同的。

如何调试:

可以采取简单步骤的数量来缩小范围,但在此之前确保您拥有包含进程所有已加载二进制文件的有效pdb文件的调试版本。

  • 要知道它是否是泄漏,监视进程“工作集”,“虚拟字节”通过任务管理器或最好通过perfmon进行计数,因为它更准确,它还提供可视图形。

  • 现在泄漏是泄漏,所以将32位系统中的地址空间增加到3 GB代替默认2 GB的步骤只能帮助一段时间,但是perfmon会告诉你是否存在稳定点,例如在少数情况下,进程需要2.2 GB的内存,因此默认情况下2 GB不够,但boot.config中的3 GB和微调3 GB的UserVA设置将有助于避免exception。

    • 如果您使用的是64位系统,那么这不是一个担心点,但要确保您的二进制文件是针对X64或任何CPU编译的,32位二进制文​​件将作为WOW进程运行,并且对64位系统也有限制。

    • 还可以尝试使用sysinternals中的一个小实用程序句柄,在批处理中多次运行它一个进程将根据分配的文件,互斥锁等句柄数提供泄漏句柄的详细信息

    • 一旦确认了真正的泄漏,而不是设置或配置或系统问题,那么内存分析器就会出现。 在免费工具中,您可以通过诸如windbg,umdh和leakdiag之类的免费工具获得大量信息,它们实际上指向了泄漏的精确堆栈跟踪。 umdh和leakdiag都是非常好的工具,它们让你知道泄漏的function。 Leakdiag比UMDH更详尽,但对于运行时堆UMDH就足够了

  • 专业内存分析器,如:

  • 点记忆 – http://www.jetbrains.com/dotmemory/

  • ant – 红门 – http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/

也非常好,我个人觉得Dot内存更有帮助,如果您有正确的符号文件,它可以帮助您快速指向泄漏的function或类型,只需很少的努力。 两者都有免费下载版本

解决内存exception大多是一个渐进和迭代的过程,因为这个过程可能隐藏在内部,每次执行都会泄漏大块内存并使整个过程陷入困境。 如果您在使用特定工具时需要帮助,请告诉我们,然后我们可以看到还可以采取哪些措施来进一步调试问题。 快乐调试

听起来像SocketIOClient DLL中有一个错误。 没有DLL我无法重现问题,但跟踪它听起来很容易。

由于C#是一种垃圾收集语言,因此获得内存不足(OOM)的唯一方法就是分配了太多内存,无法追踪到“根对象”。 有几种根对象:

  • 静态变量(或threadstatic)
  • 方法堆栈跟踪中的变量(locals / arguments)

您从这两个引用的所有对象(直接/间接)将导致您的记忆压力。 如果你分配的内存不可用,那么GC会在抛出OOM之前首先尝试释放内存; 如果GC完成后没有足够的内存可用,则会抛出OOM。

可能发生这种情况的一个明显原因是因为您运行的是32位进程,这是目前Visual Studio中的默认进程。 这可以在项目属性中修复。 但是,大多数进程不需要超过2 GB的内存,因此您更有可能在某处泄露内存。 所以让我们分解一下:

泄漏的本地人或参数

解决这类OOM的方法:

  1. 打开visual studio,ctrl d,e(或debug – > exceptions)
  2. 单击OutOfMemoryException – >选中’throw’框
  3. 运行程序。

当内存不足(OOM)命中时,您浏览堆栈跟踪(或“并行堆栈”)中的节点并检查变量的大小。 在大多数情况下,它是导致问题的单个缓冲区或集合。 F.ex. 在您的情况下,缓冲区可能会填充套接字数据,而这些数据永远不会被清空。

泄漏的静态变量

OOM的其他情况通常是粒度填充的缓冲区,并且具有对主树的引用。 找到这些的最简单方法是使用内存分析器,如Red Gate / ANTS内存分析器。 在分析器中运行程序,拍摄一些快照并检查“大型实例”。

一般来说,我通常会尽量避免使用静态变量,从而解决了整个问题。

哦,在这种情况下……

也许值得注意的是,有很多好的套接字库…尽管我不知道SocketIOClient的细节,但您可能需要考虑使用广泛支持的,经过validation的套接字库,如WCF / SOAP或Protobuf。 几乎在任何情况下都有很多关于如何使用这些内容的材料,所以如果问题出在SocketIOClient中,你可能会想到……

我猜你太频繁地运行你的计时器并且把记忆力放到一个不可持续的地方。 你尝试降低它的频率吗?

如果降低频率没有帮助,您的代码或SocketIOClient.dll库可能会泄漏内存。 我建议您首先查看该库的用法,以validation您是否未打开资源。