Vb.net 如何消除XMLSerializer/MemoryStream中的内存泄漏?

Vb.net 如何消除XMLSerializer/MemoryStream中的内存泄漏?,vb.net,memory-leaks,xml-serialization,memorystream,Vb.net,Memory Leaks,Xml Serialization,Memorystream,我在将大量数据写入XML文件时遇到一些问题。我使用以下类将对象序列化为xml,然后将其写入磁盘: ''' <summary> ''' Borrowed from http://icanmakethiswork.blogspot.ca/2012/11/xsdxml-schema-generator-xsdexe-taking.html ''' </summary> ''' <typeparam name="T"></typeparam> ''' &l

我在将大量数据写入XML文件时遇到一些问题。我使用以下类将对象序列化为xml,然后将其写入磁盘:

''' <summary>
''' Borrowed from http://icanmakethiswork.blogspot.ca/2012/11/xsdxml-schema-generator-xsdexe-taking.html
''' </summary>
''' <typeparam name="T"></typeparam>
''' <remarks></remarks>
Public Class XMLConverter(Of T)

    Private Shared serializer As XmlSerializer = Nothing

    ''' <summary>
    ''' Static constructor that initialises the serializer for this type
    ''' </summary>
    Shared Sub New()
        serializer = New XmlSerializer(GetType(T))
    End Sub

    ''' <summary>
    ''' Write a node to an xmlwriter
    ''' </summary>
    ''' <param name="writer"></param>
    ''' <param name="itemToAppend">the object to be converted and written</param>
    ''' <remarks></remarks>
    Public Shared Sub AppendToXml(writer As XmlWriter, itemToAppend As T)
        Dim strObj As String = ToXML(itemToAppend)
        strObj = XMLCleaner.CleanResult(strObj)
        writer.WriteRaw(strObj)
        writer.Flush()
        strObj = Nothing
    End Sub

    ''' <summary>
    ''' Serialize the supplied object into a string of XML
    ''' </summary>
    ''' <param name="obj"></param>
    ''' <returns></returns>
    Public Shared Function ToXML(obj As T) As String
        Dim strXml As String = ""
        Using memoryStream As New MemoryStream()
            serializer.Serialize(memoryStream, obj)
            memoryStream.Position = 0
            Using sr As New StreamReader(memoryStream)
                strXml = sr.ReadToEnd()
            End Using
        End Using
        Return strXml
    End Function

End Class

Public Class XMLCleaner
    'This is just for removing junk and slightly modifying the output
    Public Shared Function CleanResult(result As String) As String
        Dim retVal As String = Regex.Replace(result, "\sxmlns.+?"".*?""", "")
        retVal = Regex.Replace(retVal, "SavedSearchRecord", "Record")
        retVal = retVal.Replace("<?xml version=""1.0""?>", "")
        retVal = Regex.Replace(retVal, vbCrLf, vbCrLf & "    ")
        Return retVal
    End Function
End Class
问题是,当我将新记录附加到文件中时,内存正在快速积累,最终导致内存不足异常

我已经看到,不缓存序列化程序会导致这种行为,但我认为我在实现中避开了这个问题。(如果我错了,请纠正我)

检查内存转储后:

716821b4    28535     10497120 System.String
71682b74   140213    145562968 System.Char[]
71685670   140258    758802112 System.Byte[]
我可以看到,大量字节数组被卡在内存中。数组中的数据让我相信,它们被ToXML函数困在内存中(因为它们包含未修改的序列化对象字符串)

鉴于内存流位于Using块中,我无法理解为什么GC不收集这些字节数组

除此之外,内存中似乎还有大量未被收集的字符数组(大约是字节数组使用的内存的1/5)

有人能告诉我如何防止这段代码最终导致内存不足异常吗


仅供参考的代码是使用.NET 4.0编写的。我在Microsoft论坛上发布了我的问题,并在那里得到了正确的回答。我邀请回答者在这里发布他们的回复,但他们没有这样做

释义:

序列化程序创建一个程序集来序列化无法卸载的AppDomain中加载的程序集

解决方法是通过指定RootAttribute缓存序列化程序(只有一个程序集泄漏一次,这是最小内存量)

如果所有序列化对象都是相同的类型,并且要序列化的对象不是太大,则此解决方案是最好的

在我的代码中,我没有在XMLSerializer构造函数中指定RootAttribute,这会导致每次调用序列化程序时都创建一个新程序集

从此改变

''' <summary>
''' Static constructor that initialises the serializer for this type
''' </summary>
Shared Sub New()
    serializer = New XmlSerializer(GetType(T))
End Sub
“”
''初始化此类型序列化程序的静态构造函数
''' 
共享子新()
serializer=新的XmlSerializer(GetType(T))
端接头
为此:

''' <summary>
''' Static constructor that initialises the serializer for this type
''' </summary>
Shared Sub New()
    serializer = New XmlSerializer(GetType(T), XmlRootAttribute(GetType(T).ToString))
End Sub
“”
''初始化此类型序列化程序的静态构造函数
''' 
共享子新()
serializer=新的XmlSerializer(GetType(T),XmlRootAttribute(GetType(T).ToString))
端接头
彻底解决了泄漏问题

要更深入地解释为什么这样做有效,请参阅我在上面发布的链接中的原始答案

请注意,为了使结果有意义,我后来不得不对构造函数进行一些修改,但以上是修复泄漏所需的最低要求

Shared Sub New()
    Dim root As String = GetType(T).ToString
    root = root.Substring(root.LastIndexOf(".") + 1, root.Length - root.LastIndexOf(".") - 1)
    Dim rootNode As New XmlRootAttribute(root)
    rootNode.Namespace = "<appropriate.xsd>"
    serializer = New XmlSerializer(GetType(T), rootNode)
End Sub
Shared Sub New()
Dim root As String=GetType(T).ToString
root=root.Substring(root.LastIndexOf(“.”+1,root.Length-root.LastIndexOf(“.”-1)
Dim rootNode作为新的XmlRootAttribute(根)
rootNode.Namespace=“”
serializer=新的XmlSerializer(GetType(T),rootNode)
端接头
来自MSDN

为了提高性能,XML序列化基础结构动态生成程序集以序列化和反序列化指定类型。基础结构查找并重用这些程序集。此行为仅在使用以下构造函数时发生:

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(类型,字符串)


如果使用任何其他构造函数,将生成同一程序集的多个版本,并且从未卸载,这将导致内存泄漏和性能低下。最简单的解决方案是使用前面提到的两个构造函数之一。否则,必须将程序集缓存在哈希表中,如下例所示。

不是memstream,而是在该类中创建的所有字符串
CleanResult
在每次迭代中创建4个字符串,但最大的问题是
ToXML
,由于序列化,它正在重新创建越来越长的字符串。您基本上只是想在运行时将项目附加到文件中吗?这正是我想要做的。我不明白的是为什么GC没有收集字符串。是因为它是一种共享方法吗?我本以为字符串会从一个调用掉到另一个调用。让我问另一种方法:为什么你要这样做-序列化程序完全能够用更少的字符串垃圾一次序列化所有记录。我稍微修改了这个类,在一个10k循环中仍然得到了200k-250k字符串。更直截了当地做这件事更像是10万次的36MB。有很多原因可能是这样的-例如序列化程序从未超出范围,他是主要原因;您可能有一个循环,它创建它们的速度比清理它们的速度要快。很难说。如果您想查看100k替代测试代码,请告诉我。我不能完全依赖序列化程序,因为它在尝试序列化大量数据时也会生成内存不足异常。我没有考虑到范围内的序列化程序可能会导致问题。我很高兴看到你提到的10万个备选方案,我已经在这个问题上纠缠了一段时间,急于解决这个问题。
SearchRecord
有多少字段?它们大多是绳子吗?我有一些很好的内存配置文件指标,但在接下来的两天里它们都丢失了。此外,在大量数据中定义“大”—100000对我来说似乎很大……IIRC第一个构造函数没有像广告中那样重用程序集,每次我调用它时,都会创建一个新的程序集。在我最初的泄漏代码中,这是正在使用的构造函数,也是我不得不发布这个问题的原因。
Shared Sub New()
    Dim root As String = GetType(T).ToString
    root = root.Substring(root.LastIndexOf(".") + 1, root.Length - root.LastIndexOf(".") - 1)
    Dim rootNode As New XmlRootAttribute(root)
    rootNode.Namespace = "<appropriate.xsd>"
    serializer = New XmlSerializer(GetType(T), rootNode)
End Sub