为啥用C#在磁盘上写入文件后内存没有释放

Posted

技术标签:

【中文标题】为啥用C#在磁盘上写入文件后内存没有释放【英文标题】:Why the memory is not released after writing files on disk in C#为什么用C#在磁盘上写入文件后内存没有释放 【发布时间】:2012-01-31 14:25:09 【问题描述】:

我有一个复杂的算法,它从套接字连接接收数据,转换数据并尽快将其存储在 HD 上。数据,因为这个事实,我不希望处理速度减慢被存储通过使用不同的线程。 数据存储算法类似于这种结构。它本质上将 XML 保存在磁盘上。

Begin Thread
 beginthread:

 XmlTextWriter xmltextWriter;
 Save Xml file 1
 xmltextWrite.close();

 XmlTextWriter xmltextWriter;
 Save Xml file 2
 xmltextWrite.close();

 goto beginthread:
End Thread

它工作正常,但如果我查看任务管理器,我会注意到我的程序消耗的内存量随着时间的推移迅速增加(工作 1 小时后为 500mb)。这可能是合理的,因为线程并不像传入的数据那么快,而且 .NET 框架为我将所有临时存储在内存中。但我不明白的是为什么如果传入的套接字连接会停止,即使在几分钟后线程继续工作..任务管理器继续显示 500Mb 的内存..为什么内存没有释放?! XmlTextWriter 对象是一个局部变量,每次都关闭。

根据要求..这是代码的一部分

     beginthread:
        if (sleeptime < 1000) sleeptime += 2;

        try
        

            while (hashBeginConn.Count > 0)
            
                sleeptime = 0;

                int connToApply = hashBeginConn[0];

                if (olddate.ToShortDateString() != ListsockConnections[connToApply].beginDate.ToShortDateString())
                
                    JoinDataFromTempFile(ListsockConnections[connToApply].beginDate.Date.Subtract(olddate.Date).Days, false, d);
                    olddate = ListsockConnections[connToApply].beginDate.Date;
                

                if (tocreate)
                
                    // XML Serialization
                    XmlTextWriter xmltextWriter;

                    Encoding enc = null;
                    if (ListsockConnections[connToApply].ENCfromCode) enc = Encoding.GetEncoding(ListsockConnections[connToApply].codepage);
                    if (ListsockConnections[connToApply].ENCDefault) enc = Encoding.Default;
                    if (ListsockConnections[connToApply].ENCfromText) enc = Encoding.GetEncoding(ListsockConnections[connToApply].codename);
                    if (enc == null)  enc = null; 

                    // xmltextWriter = new XmlTextWriter(folderPath + "\\" + cacheFileName, enc);
                    xmltextWriter = new XmlTextWriter(DataPath + "\\_temp.xml", enc);
                    xmltextWriter.Formatting = Formatting.Indented;

                    // Start document
                    // xmltextWriter.WriteStartDocument();
                    xmltextWriter.WriteStartElement("ConnectionList");
                    xmltextWriter.WriteStartElement("connection");

                    xmltextWriter.WriteStartElement("ConnectionCounter");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].ConnectionCounter.ToString());
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("IDConnection");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].IDConnection.ToString());
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("Parsed");
                    xmltextWriter.WriteValue("false");
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("connType");
                    xmltextWriter.WriteValue("TCP/IP");
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("beginConn");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].beginDate.ToString());
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("remoteAddressFamily");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteAdressFamily);
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("remoteIP");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteIP);
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("localIP");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].localIP);
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("remoteport");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteport.ToString());
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("localport");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].localport.ToString());
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteStartElement("dataEncoding");
                    if (ListsockConnections[connToApply].codepage != 0 || ListsockConnections[connToApply].codename != "")
                    
                        if (ListsockConnections[0].codepage != 0)
                         xmltextWriter.WriteValue(ListsockConnections[connToApply].codepage.ToString()); 
                        else
                         xmltextWriter.WriteValue(ListsockConnections[connToApply].codename.ToString()); 
                    
                    else
                     xmltextWriter.WriteValue("NONE"); 
                    xmltextWriter.WriteEndElement();

                    xmltextWriter.WriteEndElement();
                    xmltextWriter.WriteEndElement();
                    xmltextWriter.Flush();
                    xmltextWriter.Close();

                    tocreate = false;
                
                else
                
                    FileInfo fi;
                    FileStream fstream;

                    //fi = new FileInfo(folderPath + "\\" + cacheFileName);
                    fi = new FileInfo(DataPath + "\\_temp.xml");
                    fstream = fi.OpenWrite();

                    XmlTextWriter xmltextWriter;

                    Encoding enc = null;
                    if (ListsockConnections[connToApply].ENCfromCode) enc = Encoding.GetEncoding(ListsockConnections[connToApply].codepage);
                    if (ListsockConnections[connToApply].ENCDefault) enc = Encoding.Default;
                    if (ListsockConnections[connToApply].ENCfromText) enc = Encoding.GetEncoding(ListsockConnections[connToApply].codename);
                    if (enc == null)  enc = null; 

                    xmltextWriter = new XmlTextWriter(fstream, enc);

                    xmltextWriter.Formatting = Formatting.Indented;

                    fstream.Position = fstream.Length - 17;

                    xmltextWriter.WriteRaw("  <connection>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <ConnectionCounter>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].ConnectionCounter.ToString());
                    xmltextWriter.WriteRaw("</ConnectionCounter>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <IDConnection>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].IDConnection.ToString());
                    xmltextWriter.WriteRaw("</IDConnection>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <Parsed>");
                    xmltextWriter.WriteValue("false");
                    xmltextWriter.WriteRaw("</Parsed>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <connType>");
                    xmltextWriter.WriteValue("TCP/IP");
                    xmltextWriter.WriteRaw("</connType>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <beginConn>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].beginDate.ToString());
                    xmltextWriter.WriteRaw("</beginConn>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <remoteAddressFamily>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteAdressFamily);
                    xmltextWriter.WriteRaw("</remoteAddressFamily>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <remoteIP>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteIP);
                    xmltextWriter.WriteRaw("</remoteIP>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <localIP>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].localIP);
                    xmltextWriter.WriteRaw("</localIP>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <remotePort>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].remoteport.ToString());
                    xmltextWriter.WriteRaw("</remotePort>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <localport>");
                    xmltextWriter.WriteValue(ListsockConnections[connToApply].localport.ToString());
                    xmltextWriter.WriteRaw("</localport>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("     <dataEncoding>");
                    if (ListsockConnections[connToApply].codepage != 0 || ListsockConnections[connToApply].codename != "")
                    
                        if (ListsockConnections[connToApply].codepage != 0)
                        
                            xmltextWriter.WriteValue(ListsockConnections[connToApply].codepage.ToString());
                        
                        else
                        
                            xmltextWriter.WriteValue(ListsockConnections[connToApply].codename.ToString());
                        
                    
                    else
                    
                        xmltextWriter.WriteValue("NONE");
                    
                    xmltextWriter.WriteRaw("</dataEncoding>" + Environment.NewLine);

                    xmltextWriter.WriteRaw("  </connection>" + Environment.NewLine);
                    xmltextWriter.WriteRaw("</ConnectionList>");

                    xmltextWriter.Flush();
                    xmltextWriter.Close();
                    fstream.Close();


                    if (fi.Length >= (maxFileTempSize * 1000000))
                    
                        JoinDataFromTempFile(0, false, enc);
                    
                

                lock (lockThis)
                
                    hashBeginConn.RemoveAt(0);
                

            

【问题讨论】:

您是否有机会分享模拟您所看到行为的实际代码或简单示例? 【参考方案1】:

许多答案说您必须调用 Dispose。尽管这些答案很好,但它们实际上并不能帮助您。您正在调用 Close,并且 Close 和 Dispose 执行相同的操作。 更好的做法是使用“使用”块,以便自动为您调用 Dispose,但您的代码可以正常使用。

您的问题的真正答案是“停止担心”。你在错误的层面上考虑这个问题。我假设您在任务管理器中查看“工作集”或“私有字节”,但您可能不理解这些实际含义。大多数人没有。这个答案给出了一个很好的总结:

What is private bytes, virtual bytes, working set?

好的,既然您知道什么是“私有字节”,那么应该更清楚为什么这不是问题了。假设您是 CLR 垃圾收集器。您代表用户分配一堆内存并使用它来存储托管对象。垃圾收集器时不时地运行,压缩内存中的对象并将以前被现在已死对象使用的内存标记为可用。 但是为什么 GC 会将所有这些内存块返回给操作系统呢? GC 有证据表明您是那种编写使用那么多内存的程序的人,所以它会保留空白页这样一来,当您在两毫秒后再次使用那么多内存时,它就不必承担再次分配它们的费用。

所以,别担心了。一切可能都很好。使用 5 亿字节的内存不是问题。如果您的虚拟内存开始变少,GC 可能会开始取消提交未使用的页面。如果没有——如果它继续无限制地增长,那就开始担心吧。

如果您仍然担心,请使用正确的工具来完成这项工作。 “私有字节”很少告诉您程序中内存实际发生的情况。 如果您想知道垃圾收集器中到底发生了什么,那么您需要使用托管内存分析器。它会为您提供确切的报告。

【讨论】:

Dispose 和 Close 未记录为具有相同的行为。见 [msdn.microsoft.com/en-us/library/system.xml.xmltextwriter.aspx]你有什么证据表明它们是相同的? 您似乎在说内存永远不会返回给操作系统,但通过观察私有字节下降,您可以看到它确实是。 (尽管可能不是全部,都是时间。)在回答您的问题“为什么 GC 会返回(内存)?”时,答案是表现良好的应用程序在使用完资源后确实应该释放资源。许多应用程序的生命周期在某些时候需要比其他时候更多的内存。应用程序在启动时需要比以后更多的内存并不是闻所未闻的。 @ElroyFlynn 你是对的,文档并没有表明Dispose()Close() 做同样的事情。碰巧他们(如果流尚未处于关闭状态,Dispose() 调用Close())但这是一个实现细节。话虽如此,这是一个与答案密切相关的细节。 Dispose() 调用 Close() 并不意味着 CLose() 完成了 Dispose() 所做的一切。 @ElroyFlynn 我应该更清楚一点:Dispose() 除了调用Close() 之外什么都不做,所以在这种情况下它们确实是等价的。【参考方案2】:

您应该致电xmltextWriter.Dispose()

此外,在垃圾收集器启动之前,内存不会被释放。您通常应该允许它自动发生,但您可以使用静态方法 GC.Collect() 显式调用它。不建议显式调用 gc.collect。让 CLR 按照自己的计划进行。

【讨论】:

为@Elroy 所说的添加更多细节-“按自己的计划”-有时GC 不会启动,直到应用程序中使用了一定数量的内存。就我而言,我发现它在达到约 50MB 之前什么都做不了,尽管听起来你目前已经远远超过了。 +1:添加:在任何实现 IDisposable 的东西周围使用 using 块(显然,在适当的情况下)。 如果没有异常发生,OP调用Close,那么他为什么要调用等效的Dispose呢? using 显然更好,但只有在发生异常时才有意义。而且手动调用Dispose肯定不比手动调用Close好。 Dispose 和 Close 未记录为具有相同的行为。请参阅 [msdn.microsoft.com/en-us/library/system.xml.xmltextwriter.aspx]您有什么证据表明它们是相同的?【参考方案3】:

xmltextWriter 持有非托管资源,在这种情况下,文件是操作系统资源。除非您明确地在其上调用Dispose(),否则它不会放弃这些资源。

最佳做法是将编写器的用法包含在using 块中,这样即使抛出异常也会自动调用Dispose()

Dispose()(或using)不保证内存会在那一刻被释放,但它确实可以防止您的应用程序将文件保持打开的时间超过需要的时间。

【讨论】:

以上是关于为啥用C#在磁盘上写入文件后内存没有释放的主要内容,如果未能解决你的问题,请参考以下文章

C# 流总结

为啥HDFS写入速度如此之慢

C语言free释放内存后为啥指针里的值不变?竟然还可以输出

为啥HDFS写入速度如此之慢

在帧写入循环中使用和不使用自动释放池的内存占用,为啥?

C零基础视频-48-文件的写入与读取