C# 导致TCP服务器内存泄漏的C字符串?

C# 导致TCP服务器内存泄漏的C字符串?,c#,string,memory,C#,String,Memory,问题简介:我创建的一个tcp服务器每秒从发送约5kb xml并断开连接的客户端接收两个连接,随着时间的推移,内存中的xml不断增长,即使我没有将xml存储在任何地方 我有一个简单的TCP服务器,其中有一个线程在while循环中调用AcceptTcpClient,然后使用新的tcpClient调用ThreadPool.QueueUserWorkItemHandleTcpClient、tcpClient 现在,在线程池runseg:HandleTcpClient的函数中,我执行以下操作: int b

问题简介:我创建的一个tcp服务器每秒从发送约5kb xml并断开连接的客户端接收两个连接,随着时间的推移,内存中的xml不断增长,即使我没有将xml存储在任何地方

我有一个简单的TCP服务器,其中有一个线程在while循环中调用AcceptTcpClient,然后使用新的tcpClient调用ThreadPool.QueueUserWorkItemHandleTcpClient、tcpClient

现在,在线程池runseg:HandleTcpClient的函数中,我执行以下操作:

int bytesRead = networkStream.Read(byteBuffer, 0, 8000);
if (bytesRead == 0) return null;
return encoder.GetString(byteBuffer, 0, bytesRead); 
之后,它调用一个函数,该函数对队列进行锁定,并将消息添加到队列中。表单上的另一个线程将每隔50ms检查一次队列,对其调用lock并弹出字符串

目前,我只是弹出字符串,然后执行以下操作:

XElement xe = XElement.Parse(msg);
xe = null;
当我启动程序时,私有设置内存为34MB,在10小时内,内存使用量为251MB。我在表单上做了一个菜单项,我可以点击它调用GC.Collect,但这只会减少2MB的内存,然后程序又开始增长

我不确定为什么内存会随着不断创建的字符串的处理而不断增长,我也不会不断地把任何东西放在堆上

我使用了.NET内存分析器,它显示字符串、字符和活动实例在不断增加。

我认为创建一个新的字节数组可能会导致内存泄漏,所以我创建了一个快速字节缓冲区类,如下所示。有没有想过为什么记忆会不断增长

class ByteBuffer  {    static Dictionary<object, byte[]> buffers = new Dictionary<object,byte[]>();

   static public void Allocate()    {
       for (int i = 0; i < 90; i++)
       {
           buffers.Add(new object(), new byte[8000]);
       }

   }

   static ASCIIEncoding encoder = new ASCIIEncoding();

   static public string GetMessage( NetworkStream ns)
    {
        foreach (object o in buffers.Keys)
        {
            bool lockTaken =false;

            Monitor.TryEnter(o,TimeSpan.FromMilliseconds(15), ref lockTaken);

            if (lockTaken)
            {
                try
                {
                    int bytesRead = ns.Read(buffers[o], 0, 8000);
                    if (bytesRead == 0) return null;
                    return encoder.GetString(buffers[o], 0, bytesRead);
                }
                catch 
                {
                    return null;
                }
                finally
                {
                    Monitor.Exit(o);
                }
            }
        }

        return null;
    } }

字符串/字符数组本身可能不是您的根本问题,它们只是一个症状。很可能存在其他对象的实例,这些对象中包含字符串或char[]字段,这些字段将被保存到


所有这些元素和名称表。条目对象看起来可疑;在将XElement设置为null之前,您确定没有泄漏对它的引用吗?您是否将它们添加到父文档中,但在它们超出范围之前忘记再次删除它们?

我不知道您的队列是如何随时间增长的,但它的容量可能在不断增长

尝试在为垃圾收集创建的同一菜单项中调用以下函数:

//Sets the capacity to the actual number of elements in the Queue<T>, if that number is less than 90 percent of current capacity.
public void TrimExcess()

从你这里得到的信息来看,没有什么是明显的原因,所以这里有一些不同的猜测:

您是否正在使用任何事件处理程序,可能您的TCP服务器正在使用SocketAsyncEventArgs?如果是,它们是否被正确地分派

您是否尝试过为XElement合并您的名称表?XElement.Parse在调用XElement.Load时在后台使用的每个XmlReader都有一个XmlNameTable,一个显式传递给构造函数,另一个由构造函数创建,具体取决于使用的构造函数重写。当它遇到字符串时,它必须处理几乎所有的前缀、本地名称、处理指令目标名称和其他一些由于性能原因存储在该表中,类似于string.Intern,但要存储到它自己的私有Intern池中,并在其哈希算法中添加一个随机元素,以抵抗基于哈希冲突的DoS攻击。如果在处理相同种类文档的读卡器之间共享名称表,在您的情况下,这意味着将解析调用替换为显式创建XmlReader,然后再调用XElement.Load,那么它有两种效果:

如果每次都看到几乎相同的元素和属性名称,则解析速度会稍微快一些

它以一种与string.Intern相当的方式改变了内存的使用,也就是说,如果你最终存储了大量不再被看到的字符串,那么事情可能会变得更糟,但是如果你每次都确实碰到相同的字符串,那么你可以使事情变得更好

NameTable不是线程安全的,但是您可以拥有一个NameTable池,在完成后将每个NameTable返回到池中,并从池中取出一个用于下一个读取器,除非池为空。基本上,您正在用n个长寿命的名称表替换大量已分配和正在消亡的名称表,其中n是应用程序一次拥有的最同步的解析线程,但可能不是第0代正在消亡的名称表。是为执行这种池而创建的类

您对TcpClient的处理是否会在完成后对其进行处理?我想这会比仅仅保留一些内存更大的影响,也就是说,我希望在保留太久的内存受到伤害之前,它会因为缺乏其他资源而崩溃,但如果他们在引用时将其写入gen 2,那么在最终确定之前可能需要很长时间,然后,当他们进入最终确定队列时,他们将成为另一个GC根,直到最终确定。虽然我看不出它有多痛,但它仍然值得一看

与联营理念相关。如果你反复看到相同的字符串,并让它们的新crea出现,那么实习是否会更好
ted副本已收集到第0代。或者,您是否有一些代码在可能不应该的情况下积极地进行实习?实习是减少内存使用的一个很好的方法,也是大幅度增加内存使用的一个更好的方法

您的内存分析器不能跟踪哪些内容包含对字符串的引用吗?如果您有这样一个屏幕截图,那会有很大帮助。服务器是否读取许多不同形式的XML,或者大多数文档的元素和属性名称是否相同?您是否手动合并了名称表?我想知道是什么调用GetMessage,以及如何管理显示代码之外的字符串。你应该在TRY块中返回吗?@Joachim:我刚开始使用内存分析器,但是我没有找到跟踪引用内容的方法Jon Hanna:有两种类型的xml被接收,但是字符串应该被处理,不是吗?Bengie:GetMessage就是这样被调用的:AddMessageToQueueByteBuffer.GetMessageClientStream;其中clientStream是在该调用上方的using块中创建的,如:usingNetworkStream clientStream=tcpClient.GetStream{…}函数AddMessageToQueue只是将传入的字符串传递给myQueue.EnQueueSomeStringToMe可疑行是return encoder.GetStringbyteBuffer,0,ByteRead;我将在try块中使用编码器,在try外部初始化字符串,在try中设置它,然后在try外部返回。如果这是C++,你会有一连串的泄漏。米迦勒,谢谢你的回应。我从队列中取出一个字符串,并将该字符串传递给执行以下操作的函数:try{XElement xe=XElement.Parsemsg;xe=null;}catch Exception{}仔细想想,这可能不会导致字符串内存问题。但是,如果要保留“垃圾收集”菜单项,则可能需要添加此项。所以我不会删除这个帖子。谢谢乔恩·汉娜的回复!在看到您的评论后,我通过创建XElement或调用parse注释掉了这些行,但内存仍然在增长。另外,我在将消息添加到队列后调用tcpClient.Close。我注意到我在5秒内获得了几百个连接,当我查看TcpView程序时,等待连接的时间有很多。@AriesOntheHorizon这是因为你没有正确处理TCP。例如,当Read返回0时,这意味着连接的另一端正在关闭-您也应该立即关闭。如果不正常关闭,TCP连接将在终止后存活约2分钟。