【问题标题】:WebRequest/WebResponse Memory leakWebRequest/WebResponse 内存泄漏
【发布时间】:2011-05-21 07:57:02
【问题描述】:

我有一个 .Net Framework #4.0 应用程序,它使用 WebRequest/WebResponse 类发出大量 Web 请求,因为我看到它存在内存泄漏(或者我做错了什么) 我写了一些简单的小应用程序来证明这一点:

class Program
{

    public static void Main(string[] args)
    {
        while(true)
        {
            var webRequest = (HttpWebRequest)WebRequest.Create("http://www.gooogle.com");
            Init(webRequest);
            using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
            {
                var responseStream = webResponse.GetResponseStream();

                responseStream.ReadTimeout = 30;
                var streamReader = new StreamReader(responseStream, Encoding.UTF8);
                var page = streamReader.ReadToEnd();

                streamReader.Close();
                streamReader.Dispose();

                responseStream.Close();
                responseStream.Dispose();

                webResponse.Close();

                Console.WriteLine("Done");

                //GC.Collect();
            }
        }
    }

     private static void Init (HttpWebRequest webRequest)
     {
         webRequest.Method = "GET";
         webRequest.Host = "www.gooogle.com";
         webRequest.UserAgent =
             "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; GTB6.5; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E; InfoPath.3) chromeframe/5.0.375.62";
         webRequest.Accept =
             "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
         webRequest.KeepAlive = true;
     }
}

我想出的唯一一个解决方案是使用 GC.Collect()(在示例中未标记),所有对象都已释放,所有流都已关闭,我是否遗漏了什么?

我发现了一些东西,但我不明白原因,如果我将控制台最小化,内存使用量会减少并且看起来还可以,这可能是什么原因,Conosole 或 WinForm 有问题,我该如何解决?

【问题讨论】:

  • 你怎么看它有内存泄漏?
  • Stream 和 StreamReader 也实现了 IDisposable。
  • 如果 GC.Collect() 修复了您的泄漏,这并不是真正的泄漏。如果不需要释放资源,GC 会挂在资源上是完全正常的。长时间运行应用程序时,您是否看到性能影响?由于内存使用量增加,是否有任何事情没有按预期执行?您是否长期分析内存使用情况?
  • 删除 while(true) 将是一个良好的开始 w.r.t 内存管理。
  • 如果您还调用 Dispose,则不需要调用 Close,但您应该将响应流和 StreamReader 放入 using 语句中。

标签: c# .net asp.net


【解决方案1】:

您在一个紧密的循环中分配内存。您可能没有内存泄漏,您的应用程序表现不佳(从某种意义上说,它无缘无故地占用了大量系统资源。)

垃圾收集器不会为了压缩释放的内存而中断循环,除非由于内存压力而不得不这样做。简单的解决方法是在循环的迭代之间引入延迟(可以像 Thread.Sleep 一样简单,但我不建议这样做。)

一旦您的程序不那么努力地消耗所有可用的 CPU 时间,它应该允许 GC 更频繁地运行。

【讨论】:

  • @Tom 您实际上是正确的,但原因是错误的。这不是一个紧密的循环,因为 GetResponse 方法调用了 IOCP wait,它可以释放进程来执行其他工作(如垃圾收集)。
  • ReadToEnd() 只是从流中读取字节到一个字符串对象中。不需要 IOCP,因为响应已经从端口读取。您可以在使用 Ethereal 或类似工具查看网络流量时使用 Read 并在每个字节之间有延迟来验证:网络流将在您完成读取之前“下载”完整响应。换句话说,它不会等到您请求数据包后才请求数据包 - GetResponse() 会立即检索整个响应。
  • 真的吗?我发现这不太可能在所有情况下都是正确的。当然,您会在较小的传输中观察到这一点,但请考虑通过 http 传输 16gb 下载的情况。或 http 音频或视频流。
  • 试试这个例如:HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); var responseStream = response.GetResponseStream(); using (var fileStream = new FileStream("C:\\temp\\myfile.avi")) { var buffer = new byte[8192]; while (true) { var bytesRead = responseStream.Read(buffer, 0, buffer.Length); if (bytesRead <= 0) break; fileStream.Write(buffer, 0, bytesRead); } }
  • 您尝试运行代码了吗?尝试无穷无尽的流。您将看到它没有在 GetResponse 读取“整个”响应。 GetResponse 只返回一个允许您从流中读取的对象。在 GetResponse 处放置一个断点并跳过它。你会看到我是对的:)
【解决方案2】:

试试这个:

while (true)
{
    var webRequest = (HttpWebRequest) WebRequest.Create("http://www.gooogle.com");
    Init(webRequest);
    using (var webResponse = (HttpWebResponse) webRequest.GetResponse())
    {
        using (var responseStream = webResponse.GetResponseStream())
        {
            responseStream.ReadTimeout = 30;
            using (var streamReader = new StreamReader(responseStream, Encoding.UTF8))
            {
                var page = streamReader.ReadToEnd();
            }
        }

        Console.WriteLine("Done");
    }
}

【讨论】:

  • "using" 与手动处理我所做的资源相同(所以我看不出它会有什么帮助),但无论如何我做了测试,结果是一样的。:(
  • "using" 发生异常时不一样。
  • @JohnSaunders 那么您将在代码中进行哪些更改以处理异常?谢谢...
  • 我不会处理该代码中的异常。没有什么可以“处理”的。没有什么可以修复的。应该只捕获可以处理的异常。日志记录应该在应用程序的顶层处理(或多或少),但这就是上面代码中的全部内容。
【解决方案3】:

看看这个 (source):

症状

当您使用HttpWebRequest 类为 HTTP POST 或 PUT 请求发送大量数据时,该请求可能会在运行 Microsoft .NET Framework 的计算机上失败。此外,您可能会收到内存不足异常。

您可能会注意到使用HttpWebRequest 类的应用程序会消耗大量内存。当您使用性能监视器监控使用 HttpWebRequest 类的应用程序时,私有字节数将随着数据的发送而不断增加。

原因

出现此问题的原因是 .NET Framework 在您使用 HttpWebRequest 类时默认缓冲传出数据。知识库文章 http://support.microsoft.com/kb/908573 记录了原始问题。

分辨率

要解决此问题,请将HttpWebRequest.AllowWriteStreamBuffering 属性设置为false。通过这样做,POST 或 PUT 请求的传出数据(实体主体)将不会在内存中缓冲。

在 Microsoft .NET Framework 4.5 之前的版本中,将 HttpWebRequest.AllowWriteStreamBuffering 属性设置为 false 有时会在将数据上传到经过身份验证的端点时导致错误。例如,您可能会遇到带有消息“此请求需要缓冲数据才能成功”的 System.Net.WebException。然而,在更深入的调查中,与异常相关的响应实际上指示了 System.Net.HttpStatusCode.Unauthorized (401) 的状态代码。知识库文章 http://support.microsoft.com/kb/908573 记录了预身份验证和 KeepAlive 连接以处理 401 响应的解决方法。

与 Microsoft .NET Framework 1.1、2.0、3.0、3.5 和 4.0 不同,Microsoft .NET Framework 4.5 为 HttpWebRequest.AllowWriteStreamBuffering 属性添加了新的设计功能。只要启用 Expect100Continue 功能,新功能就可以直接处理身份验证场景。默认ServicePointManager.Expect100Continue 值为true

【讨论】:

    【解决方案4】:

    是的,我们也发现了一个漏洞。解决方案是……小心你用这个东西做什么。考虑不使用它。我相信它有......问题(至少在 3.5 中,可能还有其他版本)。哦,别忘了在 Microsoft Connect 上报告/投票。

    【讨论】:

    • 我刚刚进行了测试,无法检测到内存泄漏。应用程序的内存在 1 分钟内增长了 5MB,之后停止。所以我认为没有内存泄漏。
    • 你有没有试过在不同的线程上创建很多?
    • 我刚刚试用了具有 5 个线程的示例应用程序,在这种情况下,它在前 1-2 分钟内将整个应用程序内存增加了 12MB,之后它在约 3 分钟内一直在工作,没有任何变化内存量。在我将GC.Collect() 添加到线程例程之后,它会减少内存消耗。所以我所有的测试都给了我类似的行为——应用程序增加了它的内存以保持不变的数量,并且没有面临永久的内存增长。但可能我的测试与你的不同?
    • 我会尽力为您提供我的断言的一些证据。结果显示这是前一段时间的,我这里没有代码。如果我没记错的话,它只是在多个线程上极其密集地使用了几个小时后才显现出来的。对于大多数应用程序来说,这不是问题。
    猜你喜欢
    • 1970-01-01
    • 2010-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2013-01-20
    • 2011-10-31
    • 2019-08-10
    相关资源
    最近更新 更多