【问题标题】:Why is it taking so long to GC System.Threading.OverlappedData?为什么 GC System.Threading.OverlappedData 需要这么长时间?
【发布时间】:2012-08-31 01:50:43
【问题描述】:

我正在通过内存分析器运行我的应用程序以检查泄漏。事情似乎还不错,但是我得到了很多 OverlappedData,它们似乎在终结器队列中徘徊,几乎什么也没做。它们是重叠 IO 的结果,已通过关闭连接任一端的底层 NetworkStream 来取消。

网络流本身被释放。任何地方都没有NetworkStream 的实时实例。

通常,它们植根于称为OverlappedDataCacheLine 的东西。我首先在回调中调用EndRead,因此如果没有对应的EndRead,则不应调用BeginRead

这是一个非常典型的外观,表明谁将其与工具隔离

最后它确实得到了 GC,但它需要很长时间 - 当我开始大约一千个流时,大约半小时才能杀死所有东西,将它们放在异步调用中到BeginRead 并在大约一分钟后关闭它们。

这个程序在一定程度上针对端口 80 上的网络服务器重现了该问题。任何网络服务器都可以。

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        var clients = new List<TcpClient>();
        for (int i = 0; i < 1000; ++i) {
            var client = new TcpClient();
            clients.Add(client);

            client.BeginConnect("localhost", 80, connectResult =>
                {
                    client.EndConnect(connectResult);

                    var stream = client.GetStream();
                    stream.BeginRead(new byte[1000], 0, 1000, result =>
                        {
                            try
                            {
                                stream.EndRead(result);
                                Console.WriteLine("Finished (should not happen)");
                            }
                            catch
                            {
                                // Expect to find an IO exception here
                                Console.WriteLine("Faulted");                               
                            }
                        }, stream);     
                }, client);             
        }

        Thread.Sleep(10000); // Make sure everything has time to connect

        foreach (var tcpClient in clients)
        {
            tcpClient.GetStream().Close();
            tcpClient.Close();
        }
        clients.Clear(); // Make sure the entire list can be GC'd

        Thread.Sleep(Timeout.Infinite); // Wait forever. See in profiler to see the overlapped IO take forever to go away
    }
}

当然,这个程序不需要永远清理一千个OverlappedData,因为它比实际应用程序小得多,但它确实需要一段时间才能完成它的工作。当我运行我的真实东西而不是这个测试应用程序时,我收到了关于卡住的终结器的警告。它在我的应用程序中没有多大作用,只是尝试关闭所有可能尚未关闭的内容,并确保没有任何引用被保留在任何地方。

如果我在客户端上调用 Dispose()Close() 并且它是流,这似乎并不重要。结果是一样的。

关于为什么会发生这种情况以及如何避免这种情况的任何线索? CLR 对我很聪明,并保持这些固定的内存块完好无损以准备新的调用?为什么终结器的完成速度如此之慢?

更新 在通过在 F5 键上放一杯水并喝杯咖啡进行了一些非常愚蠢的负载测试之后,似乎有什么东西在压力下触发了一个更完整的 GC 来收集这些东西。所以实际上 似乎 不是一个真正的问题,但仍然很高兴知道这里实际发生了什么以及为什么收集这个对象比其他对象慢很多,如果这可能是一个后期会出现内存碎片等问题。

【问题讨论】:

  • 调用EndXxxx失败导致资源泄露10分钟。
  • 那么,如果在您等待回调被调用时底层流对您关闭,您是否可以以任何方式使用EndXxx,考虑到除了回调之外没有任何等待?我的意思是,如果出现问题,回调仍然会被调用,不是吗?
  • 关闭套接字将完成操作。 EndRead 将清理这些资源并生成 ObjectDisposed 异常。只需确保您仍然在真实代码中调用 EndRead。
  • @HansPassant 这就是我所做的。我的意思是,它最终会被正确清理,只是需要很长时间。这不是疯狂的内存量,但如果您在服务器上放置大量负载,则有几千个这样的对象漂浮在周围。 GC 似乎不时地挑选一些,据我所见,它们的大小只有大约 120 字节。从我停止加载到它开始处理这些对象,没有 10 分钟的延迟。处置会尽快开始,但需要很长时间才能完成。其他对象的处理速度要快得多。
  • 那么,GC 在中止后实际运行的频率是多少?通常,当您停止做某事时,程序会停止分配内存。就像您不再有实时连接一样。这也会停止收集。

标签: c# memory-leaks garbage-collection asyncsocket


【解决方案1】:

好的,现在似乎很清楚发生了什么。垃圾回收仅在您分配内存时发生。它需要至少 2 MB 的分配空间,这是触发 GC 的第 0 代 GC 堆的典型初始大小。换句话说,一个什么都不做的程序永远不会运行 GC,并且您会看到任何尚未在堆中使用内存分析器收集很长时间的对象。

这很好地解释了您所描述的内容。终止所有连接后,您的程序不再需要执行任何操作。因此,如果有内存,则不会分配太多,因此不会触发集合。如果您的探查器未显示集合,那么您可以使用 Perfmon.exe 查看它们。否则这根本不是问题,只是垃圾收集器工作方式的副作用。

只有当您有明确的证据表明程序存在资源消耗问题时才担心泄漏。

【讨论】:

  • 谢谢!我对此进行了测试,并进行了检查。我不知道这种行为。我有一个与此无关的内存泄漏,在分析应用程序以找到它时,我想知道这是否是原因。
【解决方案2】:

尝试从AsyncResult 获取客户端以排除任何 lambda 闭包问题。

TcpClient t = (TcpClient)connectResult.AsyncState;

另外,你不应该打电话给EndConnect 来完成你的处理吗?

【讨论】:

  • 没有一个实例植根于Closure,将其更改为使用AsyncState 不会改变任何东西。它们在最终确定队列中正确浮动。 EndConnect 应该在那里调用,否则异步操作将无法完成。改变它没有任何区别。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
  • 2011-12-07
  • 2011-10-26
  • 2021-11-27
相关资源
最近更新 更多