2014-11-28 1 views
0

У меня возникли проблемы с записью большого набора данных в файл XML. Я использую следующий класс для сериализации объектов в XML, а затем записать их на диск:Как устранить утечку памяти в моем XMLSerializer/MemoryStream?

''' <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 

И зову это так:

XMLConverter(Of SavedSearchRecord).AppendToXml(writer, record) 

Проблема в том, что память быстро накапливается, как я добавлять новые записи в файл и в конечном итоге приводить к исключению из памяти.

Я видел, что не кэширование сериализатора может привести к такому поведению, но я думаю, что я обошел эту проблему в своей реализации. (Пожалуйста, поправьте меня, если я ошибаюсь).

После изучения дампа памяти:

716821b4 28535  10497120 System.String 
71682b74 140213 145562968 System.Char[] 
71685670 140258 758802112 System.Byte[] 

Я вижу, что у меня есть огромное количество байтовых массивов застревать в памяти. Данные в массивах заставляют меня думать, что они застряли в памяти с помощью функции ToXML (поскольку они содержат неизмененные сериализованные строки объектов).

Учитывая, что поток памяти находится в блоке Using, я не могу понять, почему эти байтовые массивы не собираются GC.

В дополнение к этому также имеется большое количество массивов Char в памяти (около 1/5 из памяти, используемой массивами байтов), которые не собираются.

Может ли кто-нибудь сказать мне, как предотвратить этот код от кульминации исключений из памяти?

FYI код написан с использованием .NET 4.0

+0

Это не memstream, а все строки, которые вы создаете в этом классе. 'CleanResult' создает 4 на итерацию, но большой боров - это' ToXML', который воссоздает более длинные и длинные строки в результате сериализации. Вы в основном просто пытаетесь добавить элементы в файл, когда идете? – Plutonix

+0

Это именно то, что я пытаюсь сделать. Я не понимаю, почему строки не собираются GC. Это потому, что это общий метод? Я бы подумал, что строки будут выпадать из области от вызова до звонка. – hobwell

+0

Позвольте мне спросить по-другому: ** почему ** вы делаете это именно так - сериализатор вполне способен сериализовать все записи одновременно с гораздо меньшим количеством мусора строки. Я немного изменил класс и по-прежнему получил строки 200k-250k всего за 10k циклов. Делать это более прямолинейно было больше, чем 36MB за 100 000 повторений. Есть несколько причин, по которым это может быть так - например, сериализатор никогда не выходит за рамки сферы, и он является основной причиной; у вас может быть цикл, который создает их быстрее, чем может очистить. Сложно сказать. Дайте мне знать, если вы хотите увидеть альтернативный тестовый код 100k. – Plutonix

ответ

0

Я отправил свой вопрос в форуме Microsoft, и это было правильно там ответили. Я пригласил ответчика опубликовать свой ответ здесь, но они этого не сделали.

The original answer in full can be found here

Перефразирование:

Сериализатор создает сборку для сериализации загруженной сборки в AppDomain, которые не могут быть выгружены.

Обойти было Кэшируйте сериалайзер, указав RootAttribute (только один узел будет течь, когда, что минимальный объем памяти)

Это решение является наилучшим, если все сериализованные объекты имеют одинаковый тип и сериализуемый объект не слишком велик.

В моем коде у меня не было RootAttribute, указанного в конструкторе XMLSerializer, который создавал новую сборку каждый раз, когда я вызывал сериализатор.

Переход от этого

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

К этому:

''' <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 

Полностью решен протечки.

Для более подробного объяснения причин, почему это сработало, см. Оригинальный ответ по ссылке, которую я разместил выше.

Обратите внимание, что позже мне пришлось немного модифицировать конструктор, чтобы результаты имели смысл, но приведенное выше было минимальным требованием для устранения утечки.

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 
0

Из MSDN https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer(v=vs.110).aspx

Для повышения производительности, то XML сериализация инфраструктура динамически создает сборки для сериализации и десериализации указанных типов. Инфраструктура находит и повторно использует эти сборки. Это происходит только при использовании следующих конструкторов:

XmlSerializer.XmlSerializer (тип)

XmlSerializer.XmlSerializer (тип, String)

Если вы используете какой-либо из других конструкторов, несколько версий одного и того же сборка генерируется и никогда не выгружается, что приводит к утечке памяти и низкой производительности. Самое простое решение - использовать один из ранее упомянутых двух конструкторов. В противном случае вы должны кэшировать сборки в Hashtable, как показано в следующем примере.

+0

IIRC, что первый конструктор не повторно использовал сборки, как рекламируется, каждый раз, когда я его вызывал, была создана новая сборка. В моем исходном коде с утечкой, это был используемый конструктор, и именно поэтому я должен был опубликовать этот вопрос. – hobwell