.NET XmlSerializer memory leak

Home / .NET XmlSerializer memory leak

A memory leak can very difficult to isolate. I ran into an interesting one a few days ago. I have a service that makes calls to a SOAP service with a serialized SOAP body from POCO, and it digests/deserializes the Xml response into POCO. However, there was a memory leak that I could not locate.


It seems like I tried everything to fix this memory leak. I rearranged code to ensure contexts were properly disposed and no EF DbContext change tracking was holding onto objects, I pointed fingers at the Ninject scope, forced garbage collection, etc etc. However, even after making changes to force clearing of memory, making scopes unique, etc etc, memory usage was still growing. After a bit of memory profiling, I noticed two odd sources of memory usage:

This got me thinking. Surely the built-in .NET Xml serialization didn’t have a memory leak? After a bit more research, though, I stumbled upon this old article:

https://www-jo.se/f.pfleger/memoryleak

In the code, I’m using an XmlSerializer to build up the SOAP body used for the request. It just so happened that I was using this constructor for flexibility in how I build the SOAP body:

public XmlSerializer(Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace);

And then there was the “aha!” moment. This explained why forcing GC and such had no impact. The instantiation of XmlSerializers was creating tons of dynamically generated assemblies that couldn’t be released from memory. In reality, though, most of the serializers I was using could have easily been reused. Thus, I added a bit of code to cache the serializers in a static dictionary.

public static class StaticMethods
{
public static object _lock = new object();
public static Dictionary<string, XmlSerializer> _serializers = new Dictionary<string, XmlSerializer>();

public static XmlSerializer GetSerializer<T>(string rootName, XNamespace xs)
{
    lock (_lock)
    {
        var key = $"{typeof(T)}|{rootName}|{xs}";
        if (!_serializers.TryGetValue(key, out XmlSerializer serializer))
        {
                if (!string.IsNullOrWhiteSpace(rootName) && xs == null)
            {
                serializer = new XmlSerializer(typeof(T), null, null, new XmlRootAttribute(rootName), string.Empty);
            }
            else
            {
                serializer = new XmlSerializer(typeof(T), null, null, new XmlRootAttribute(rootName), xs.ToString());
            }

            _serializers.Add(key, serializer);
        }

        return serializer;
    }
}

With this change in place, I let my service run, repeating every ~ 30 seconds or so. After 24+ hours of running, the memory leak appeared to be gone. I was pleased to finally put this leak behind me, but it still seems very weird that it exists to begin with.

Leave a Reply