为什么这段代码会导致内存泄漏?

8

最近我接手了一个WCF Windows Service,它大量使用以下静态实用程序类来检索查找数据:

   public static class Utility
    {

       //begin code that causes increased memory consumption
        private static Dictionary<string, ErrorData> _errorData;

        internal static Dictionary<string, ErrorData> ErrorData

        {
            get
            {
                if (_errorData == null)
                {
                    _errorData = GetErrorData();
                }
                return _errorData;
            }

        }
        //end code that causes increased memory consumption

        /// GetErrorData method to get error messages from error xml
        /// </summary>         
        /// <returns>Dictionary of Error messages value for different fields.</returns>           
        internal static Dictionary<string, ErrorData> GetErrorData()
        {
            Dictionary<string, ErrorData> data = null;


                XmlDocument doc = LoadXmlDocument(Constants.ErrorMessagesFileName);
                XmlNodeList errorNode = doc.SelectNodes("/ErrorMessages/Error");
                data = new Dictionary<string, ErrorData>();

                foreach (XmlNode node in errorNode)
                {
                    ErrorData errorValues = new ErrorData();
                    errorValues.FieldName = node.Attributes["FieldName"].Value;
                    errorValues.ErrorMessage = node.Attributes["ErrorMessage"].Value;
                    data.Add(node.Attributes["code"].Value, errorValues);
                }


            return data;
        }
        internal static XmlDocument LoadXmlDocument(string xmlFileName)
        {
            XmlDocument doc = null;
            try
            {
                if (HttpRuntime.Cache[xmlFileName] == null)
                {
                    doc = new XmlDocument();
                    doc.Load(Constants.Folderpath + "\\" + xmlFileName);
                    HttpRuntime.Cache.Insert(xmlFileName, doc);
                }
                else
                {
                    doc = (XmlDocument)HttpRuntime.Cache[xmlFileName];
                }
            }
            catch (Exception ex)
            {
               //log
            }
            return doc;
        }
    }

正如您所看到的,静态 ErrorData 属性使用了一个私有的后备字段。ErrorData 是一个字典,它是使用文件系统上的 XML 资源构建的,这就是为什么文件的内容在初始检索时存储在 HttpRuntime.Cache 中的原因。
在正常负载下,该服务大约消耗 120MB 的 RAM。
在某个时候,团队成员感觉需要引入另一级优化,通过创建由懒加载静态字段支持的静态属性来实现。无论如何,存在该静态字段会导致内存泄漏(500MB+),仅在调用几次服务后就会出现。
一旦我删除静态字段和属性(客户端改为调用 Utility.GetErrorData()),内存消耗就会恢复到正常水平。
有人能解释一下为什么存在这个静态字段会导致内存泄漏吗?如果有区别的话,WCF 服务正在运行 InstanceContextMode.PerCall。
非常感谢。

你怎么知道它是导致内存泄漏的呢?它不仅仅是使用了大量内存并将其保留为静态吗?你的服务实例化是什么?是PerCall?Singleton?Session? - Sam Holder
1
这到底是内存泄漏还是垃圾回收器没有运行足够的次数,或者认为不需要释放内存呢? - ta.speot.is
1
你是在DEBUG模式还是RELEASE模式下运行?DEBUG模式会出现RELEASE模式不会出现的泄漏情况。 - Felan
1
你应该发布“惰性加载静态字段”的实现,因为这是你要求我们诊断的内容。 - David Nelson
4个回答

1

如果错误文件非常大,静态版本会将巨大的XML文档加载到内存中,并永远不会释放它。以前,如果客户端调用了GetErrorData(),那么数据就会被加载到内存中并返回,从而清除内存。

这里没有同步,因此如果静态变量未加载,则几个同时请求将分别开始加载错误文档。只有一个字典会获胜并保存到静态变量中。但是,如果错误文件很大,则同时加载它的多个线程会增加内存压力。如果是这种情况,我希望下一次垃圾回收会回收额外的实例并释放大部分内存。

另请注意,静态实例版本仅加载一次错误文件。因此,如果创建了其他错误,这些错误将永远不会返回给客户端。


是的,而且由于没有同步,多个竞争的 GetErrorData 调用最终都会将它们的实例添加到 Http 缓存中,因此可能会在内存中存储多个相同的 XML 数据副本。 - Mahol25

0

添加同步是否可以解决“内存泄漏”问题?

也就是说,例如将LoadXmlDocument()和GetErrorData()都设置为私有,并修改ErrorData属性,类似于以下方式:

    private static Dictionary<string, ErrorData> _errorData;
    private static object lockObject = new object();

    internal static Dictionary<string, ErrorData> ErrorData
    {
        get
        {
            lock (lockObject)
            {
                if (_errorData == null)
                {
                    _errorData = GetErrorData();
                }
                return _errorData;
            }
        }

    }

注意:通常,内存泄漏意味着应用程序随着时间的推移慢慢地消耗更多的内存(这些内存永远不会被回收)。您是否正在观察到这种情况,或者当您更改实现时,您的内存消耗只是变得更高但保持稳定?要真正验证您确实存在内存泄漏以及真正的原因是什么(哪些对象无法被收集/完成),您通常需要使用内存分析器。

0

当你谈到“变化”时,我并不完全确定你指的是哪个代码更改。 然而,从阅读代码来看,我的猜测是您最终会多次调用GetErrorData,并且字典只会填充大量重复的条目。 如果您添加日志记录代码,哪些代码会被重复输入?

Martyn


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接