使用非常大的字符串时C#中的内存泄漏

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用非常大的字符串时C#中的内存泄漏相关的知识,希望对你有一定的参考价值。

检查我的一个应用程序的内存泄漏,我发现下一个代码“表现得很奇怪”。

public String DoTest()
        {
            String fileContent = "";
            String fileName = "";
            String[] filesNames = System.IO.Directory.GetFiles(logDir);
            List<String> contents = new List<string>();

            for (int i = 0; i < filesNames.Length; i++)
            {
                fileName = filesNames[i];
                if (fileName.ToLower().Contains("aud"))
                {
                    contents.Add(System.IO.File.ReadAllText(fileName));
                }
            }
            fileContent = String.Join("", contents);
            return fileContent;
        }

在运行这段代码之前,对象使用的内存大约为1.4 Mb。一旦调用此方法,它就使用了70MB。等待几分钟,没有任何改变(原始物体很久以前就被释放了)。 打电话给

GC.Collect();
GC.WaitForFullGCComplete();

内存减少到21MB(然而,远远超过开始时的1.4MB)。

使用控制台应用程序(无限循环)和winform应用程序进行测试。即使在直接呼叫时也会发生(不需要创建更多对象)。

编辑:完整代码(控制台应用程序)以显示问题

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace memory_tester
{

    /// <summary>
    /// Class to show loosing of memory
    /// </summary>
    class memory_leacker
    {
        // path to folder with 250 text files, total of 80MB of text
        const String logDir = @"d:http_server_testhttp_server_testinDebuglogs";

        /// <summary>
        /// Collecting all text from files in folder logDir and returns it.
        /// </summary>
        /// <returns></returns>
        public String DoTest()
        {
            String fileContent = "";
            String fileName = "";
            String[] filesNames = System.IO.Directory.GetFiles(logDir);
            List<String> contents = new List<string>();

            for (int i = 0; i < filesNames.Length; i++)
            {
                fileName = filesNames[i];
                if (fileName.ToLower().Contains("aud"))
                {
                    //using string builder directly into fileContent shows same results.
                    contents.Add(System.IO.File.ReadAllText(fileName));

                }
            }
            fileContent = String.Join("", contents);
            return fileContent;
        }

        /// <summary>
        /// demo call to see that no memory leaks here
        /// </summary>
        /// <returns></returns>
        public String DoTestDemo()
        {
            return "";
        }

    }

    class Program
    {
        /// <summary>
        /// Get current proc's private memory
        /// </summary>
        /// <returns></returns>
        public static long GetUsedMemory()
        {
            String procName = System.AppDomain.CurrentDomain.FriendlyName;
            long mem = Process.GetCurrentProcess().PrivateMemorySize64 ;
            return mem;
        }

        static void Main(string[] args)
        {
            const long waitTime = 10;  //was 240
            memory_leacker mleaker = new memory_leacker();

            for (int i=0; i< waitTime; i++)
            {
                Console.Write($"Memory before {GetUsedMemory()} Please wait {i}
");
                Thread.Sleep(1000);
            }
            Console.Write("
");

            mleaker.DoTestDemo();
            for (int i = 0; i < waitTime; i++)
            {
                Console.Write($"Memory after demo call {GetUsedMemory()} Please wait {i}
");
                Thread.Sleep(1000);
            }
            Console.Write("
");

            mleaker.DoTest();
            for (int i = 0; i < waitTime; i++)
            {
                Console.Write($"Memory after real call {GetUsedMemory()} Please wait {i}
");
                Thread.Sleep(1000);
            }
            Console.Write("
");

            mleaker = null;
            for (int i = 0; i < waitTime; i++)
            {
                Console.Write($"Memory after release objectg {GetUsedMemory()} Please wait {i}
");
                Thread.Sleep(1000);
            }
            Console.Write("
");

            GC.Collect();
            GC.WaitForFullGCComplete();
            for (int i = 0; i < waitTime; i++)
            {
                Console.Write($"Memory after GC {GetUsedMemory()} Please wait {i}
");
                Thread.Sleep(1000);
            }
            Console.Write("
...pause...");

            Console.ReadKey();
        }
    }
}
答案

我相信如果你在fileContent上使用stringbuilder而不是字符串,你可以提高你的性能和内存使用率。

public String DoTest()
        {
            var fileContent = new StringBuilder();
            String fileName = "";
            String[] filesNames = System.IO.Directory.GetFiles(logDir);
            for (int i = 0; i < filesNames.Length; i++)
            {
                fileName = filesNames[i];
                if (fileName.ToLower().Contains("aud"))
                {
                    fileContent.Append(System.IO.File.ReadAllText(fileName));
                }
            }

            return fileContent;
        }
另一答案

我在下面重构了你的代码版本,在这里我已经删除了原始问题中名为'contents'的字符串列表的需要。

    public String DoTest()
    {
        string fileContent = "";

        IEnumerable<string> filesNames = System.IO.Directory.GetFiles(logDir).Where(x => x.ToLower().Contains("aud"));

        foreach (var fileName in filesNames)
        {
            fileContent = string.Join("", System.IO.File.ReadAllText(fileName));
        }

        return fileContent;
    }

以上是关于使用非常大的字符串时C#中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

iTextSharp - 非常大的表内存泄漏

如何解决内存泄漏问题?

避免android片段中内存泄漏的最佳方法是啥

当代码无法释放内存时,它是不是是 C 中的内存泄漏,但操作系统仍然会?

返回一个 C++ std::string 对象是不是可以避免内存泄漏?

使用导致内存泄漏的音频片段