当我运行此代码时,内存使用量不断增长。
while (true)
{
try
{
foreach (string sym in stringlist)
{
StreamReader r = new StreamReader(@"C:\Program Files\" + sym + ".xml");
XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));
XMLObj obj = (XMLObj)xml.Deserialize(r);
obj.Dispose();
r.Dispose();
r.Close();
}
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Thread.Sleep(1000);
Console.Clear();
}
XMLObj是自定义对象
[Serializable()]
public class XMLObj: IDisposable
{
[XmlElement("block")]
public List<XMLnode> nodes{ get; set; }
public XMLObj() { }
public void Dispose()
{
nodes.ForEach(n => n.Dispose());
nodes= null;
GC.SuppressFinalize(this);
}
}
我尝试添加GC.Collect() ;但这似乎无能为力。
#1 楼
泄漏在这里:new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))
XmlSerializer
使用程序集生成,并且无法收集程序集。对于最简单的构造方法(new XmlSerializer(Type)
等),它会执行一些自动缓存/重用操作,但对于这种情况则不会。因此,您应该手动缓存它:static readonly XmlSerializer mySerializer =
new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))
,并使用缓存的序列化程序实例。
评论
是否只有某些构造函数使用缓存背后有任何逻辑?
–莱昂尼德·瓦西廖夫
2015年11月12日上午10:50
@LeonidVasilyev可能很方便;缓存单个场景的东西很容易;要执行更复杂的场景,将涉及构造哈希算法和健壮的相等性检查以比较不同的配置,以与一些类似的哈希表配合使用
– Marc Gravell♦
15年11月12日在11:54
毫无疑问的代码行也导致了我的应用程序中的内存泄漏。我已经确认了很棒的发现!
–丹
16年2月10日在18:20
如果您处于foreach循环中,请在循环之前对其进行初始化(XmlSerializer mySerializer = new ..),然后将对象(mySerializer)传递给用于反序列化的方法
–山图
19 Mar 15 '19在14:33
#2 楼
首先,即使抛出异常(与XMLObj相同),也应该处置StreamReader。使用using
语句。当前,当引发异常时,您将不会处理。内存泄漏的可能性很小。更有可能的是,运行时根本还没有选择收集内存。即使是GC.Collect也不一定会释放内存。
处理非常大的XML文件(多GB)时,我遇到了类似的情况。即使运行时抢占了大多数可用内存,它也会在内存压力许可时释放它。
您可以在Visual Studio中使用内存探查器来查看分配了什么内存以及它驻留在哪代中。 br />
UPDATE
@KaiEichinger的评论值得调查。它表示XmlSerializer可能为每次循环迭代创建新的缓存对象定义。
XMLSerializer构造函数为要使用反射序列化的类型创建临时程序集,由于代码生成成本很高,因此,程序集按类型缓存在内存中。但是很多时候根名会被更改并且可以是动态的,并且不会缓存动态程序集。因此,每当调用上述代码行时,它每次都会加载新程序集,并将保留在内存中,直到卸载AppDomain。
评论
这工作了!非常感谢。 XmlSerializer的构造函数在循环中是一个问题。一旦取出,内存就会稳定。
– Alex999
2014年5月27日19:26
#3 楼
从MSDN:在此处输入链接描述为了提高性能,XML序列化基础结构动态生成程序集以序列化和反序列化指定的类型。基础结构查找并重用这些程序集。仅当使用以下构造函数时,才会发生此行为:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type,String)
如果使用任何其他构造函数,则同一程序集的多个版本会生成并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,必须将程序集缓存在哈希表中,如以下示例所示。
=>因此,要解决此问题,必须使用此构造函数
XmlSerializer xml = new XmlSerializer(typeof(XMLObj))
而不是XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));
并将根XML属性添加到XMLObj类中。
[Serializable()]
[XmlRoot("root")]
public class XMLObj: IDisposable
{
[XmlElement("block")]
public List<XMLnode> nodes{ get; set; }
public XMLObj() { }
public void Dispose()
{
nodes.ForEach(n => n.Dispose());
nodes= null;
GC.SuppressFinalize(this);
}
}
#4 楼
我使用“缓存”类来避免在每次需要序列化某些东西时实例化xmlserializer(还添加了一个XmlCommentAttribute,用于向xml输出中的序列化属性添加注释),对我来说,它的工作原理类似于Sharm,希望对此有所帮助: public static class XmlSerializerCache
{
private static object Locker = new object();
private static Dictionary<string, XmlSerializer> SerializerCacheForUtils = new Dictionary<string, XmlSerializer>();
public static XmlSerializer GetSerializer<T>()
{
return GetSerializer<T>(null);
}
public static XmlSerializer GetSerializer<T>(Type[] ExtraTypes)
{
return GetSerializer(typeof(T), ExtraTypes);
}
public static XmlSerializer GetSerializer(Type MainTypeForSerialization)
{
return GetSerializer(MainTypeForSerialization, null);
}
public static XmlSerializer GetSerializer(Type MainTypeForSerialization, Type[] ExtraTypes)
{
string Signature = MainTypeForSerialization.FullName;
if (ExtraTypes != null)
{
foreach (Type Tp in ExtraTypes)
Signature += "-" + Tp.FullName;
}
XmlSerializer XmlEventSerializer;
if (SerializerCacheForUtils.ContainsKey(Signature))
XmlEventSerializer = SerializerCacheForUtils[Signature];
else
{
if (ExtraTypes == null)
XmlEventSerializer = new XmlSerializer(MainTypeForSerialization);
else
XmlEventSerializer = new XmlSerializer(MainTypeForSerialization, ExtraTypes);
SerializerCacheForUtils.Add(Signature, XmlEventSerializer);
}
return XmlEventSerializer;
}
public static T Deserialize<T>(XDocument XmlData)
{
return Deserialize<T>(XmlData, null);
}
public static T Deserialize<T>(XDocument XmlData, Type[] ExtraTypes)
{
lock (Locker)
{
T Result = default(T);
try
{
XmlReader XmlReader = XmlData.Root.CreateReader();
XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
Result = (T)Ser.Deserialize(XmlReader);
XmlReader.Dispose();
return Result;
}
catch (Exception Ex)
{
throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
}
}
}
public static T Deserialize<T>(string XmlData)
{
return Deserialize<T>(XmlData, null);
}
public static T Deserialize<T>(string XmlData, Type[] ExtraTypes)
{
lock (Locker)
{
T Result = default(T);
try
{
using (MemoryStream Stream = new MemoryStream())
{
using (StreamWriter Writer = new StreamWriter(Stream))
{
Writer.Write(XmlData);
Writer.Flush();
Stream.Position = 0;
XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
Result = (T)Ser.Deserialize(Stream);
Writer.Close();
}
}
return Result;
}
catch (Exception Ex)
{
throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
}
}
}
public static XDocument Serialize<T>(T Object)
{
return Serialize<T>(Object, null);
}
public static XDocument Serialize<T>(T Object, Type[] ExtraTypes)
{
lock (Locker)
{
XDocument Xml = null;
try
{
using (MemoryStream stream = new MemoryStream())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
using (StreamReader Reader = new StreamReader(stream))
{
XmlSerializer Serializer = GetSerializer<T>(ExtraTypes);
var settings = new XmlWriterSettings { Indent = true };
using (var w = XmlWriter.Create(stream, settings))
{
Serializer.Serialize(w, Object, ns);
w.Flush();
stream.Position = 0;
}
Xml = XDocument.Load(Reader, LoadOptions.None);
foreach (XElement Ele in Xml.Root.Descendants())
{
PropertyInfo PI = typeof(T).GetProperty(Ele.Name.LocalName);
if (PI != null && PI.IsDefined(typeof(XmlCommentAttribute), false))
Xml.AddFirst(new XComment(PI.Name + ": " + PI.GetCustomAttributes(typeof(XmlCommentAttribute), false).Cast<XmlCommentAttribute>().Single().Value));
}
Reader.Close();
}
}
return Xml;
}
catch (Exception Ex)
{
throw new Exception("Could not serialize from " + typeof(T).Name + " to xml string", Ex);
}
}
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
public string Value { get; set; }
}
#5 楼
我认为将XMLSerializer
构造函数移到循环外并缓存其结果将解决此问题,在此进行解释评论
循环之外是不够的:它应该缓存为静态IMO
– Marc Gravell♦
2014年5月27日19:26
评论
如果您认为GC.Collect解决了“内存泄漏”,那么您找错了地方:blogs.msdn.com/b/ricom/archive/2003/12/02/40780.aspx。仅仅因为内存使用量增加,并不意味着您有内存泄漏。我还建议对垃圾收集进行研究是常规的:msdn.microsoft.com/zh-cn/library/0xy59wtx(v=vs.110).aspxXmlNode不实现IDisposable。如果您的班级没有终结器,则无需调用GC.SuppressFinalize。使用PerfView(microsoft.com/zh-cn/download/details.aspx?id=28567)检查谁拥有您的内存。我认为您发布的代码完全没有泄漏。
这不是问题的答案,但请考虑将XmlSerializer实例的构造移到循环外。这可能会提高性能并减少内存开销。
我在这里遇到了一篇很棒的博客文章,该文章解释了XmlSerializer为什么会导致内存泄漏:techknackblogs.com/2012/10/xmlserializer-may-cause-memory-leak另外,您还应该使用using(){}语句即使有例外,这也会照顾您的资源。在您的代码中,如果引发Exception,则不会处理资源。
@DanBryant您的建议尽管从预编译的角度来看是准确的,但您会惊讶于现在的高级编译器如何(我进行了一些测试,发现对于某些foreach而言,在内部或外部定义变量在IL中没有区别) 。