.NET FontFamily memory leak on Windows 10
Asked Answered
C

3

4

On Windows 10, the System.Drawing.FontFamily.IsStyleAvailable method seems to leave the allocated space into memory even after the Dispose method has been called.

I wrote a simple console application to test it:

using System;
using System.Drawing;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static string getMemoryStatusString()
        {
            using (Process p = Process.GetCurrentProcess())
            {
                return "(p: " + p.PrivateMemorySize64 + ", v:" + p.VirtualMemorySize64 + ")";
            }
        }

        static void Main(string[] args)
        {
            string s = getMemoryStatusString();
            foreach(FontFamily fontFamily in FontFamily.Families)
            {
                Console.Write(fontFamily.Name + " " + getMemoryStatusString() + " -> ");

                fontFamily.IsStyleAvailable(FontStyle.Regular);
                fontFamily.Dispose();

                Console.WriteLine(getMemoryStatusString());
            }
            string e = getMemoryStatusString();
            Console.WriteLine(s + " -> " + e);
            Console.ReadLine();
        }
    }
}

Any idea on why this is happening?

Thanks in advance!

Cryptozoite answered 29/10, 2015 at 14:18 Comment(3)
You really should not be disposing fontFamily, you don't own the object, it is still a member of the Familes collection. Any future enumerations of that collection will possibly error out.Equalizer
This is simply not how memory management works in Windows. Released address space very rarely gets returned back to the operating system. It gets added back to a "free list", available for future allocations. Taking no resources, just a single number for every 4096 bytes. The benefits of a demand-paged virtual memory operating system. Only when a blue moon enters the quadrant of Aquarius and the free space coalesces to a large enough chunk of VM space does it get returned.Smorgasbord
I'm calling Dispose just to highlight the problem.Cryptozoite
D
0

If there is a memory leak it would be in gdiplus.dll, FontFamily.IsStyleAvailable() actually makes an extern call to GdipIsStyleAvailable().

From ILSpy:

public bool IsStyleAvailable(FontStyle style)
{
    int num2;
    int num = SafeNativeMethods.Gdip.GdipIsStyleAvailable(new HandleRef(this, this.NativeFamily), style, out num2);
    if (num != 0)
    {
        throw SafeNativeMethods.Gdip.StatusException(num);
    }
    return num2 != 0;
}

Which is in turn defined as:

[DllImport("gdiplus.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
internal static extern int GdipIsStyleAvailable(HandleRef family, FontStyle style, out int isStyleAvailable);
Diner answered 29/10, 2015 at 14:23 Comment(0)
I
0

I can't see any leak on my workstation (I use Windows 7 with .Net 4.5, however). There're some issues on the testing procedure

static string getMemoryStatusString() {
  // Do not forget to collect the garbage (all the generations)
  GC.Collect(2);
  GC.WaitForFullGCComplete(); 

  using (Process p = Process.GetCurrentProcess()) {
    return "(p: " + p.PrivateMemorySize64 + ", v:" + p.VirtualMemorySize64 + ")";
  }
}

// Suspected method
static void methodUnderTest() {
  foreach (FontFamily fontFamily in FontFamily.Families) {
    //Console.Write(fontFamily.Name + " " + getMemoryStatusString() + " -> ");
    fontFamily.IsStyleAvailable(FontStyle.Regular);
    //TODO: You must not do this: disposing instanse you don't own  
    fontFamily.Dispose(); 
  }
}

// Test itself
static void Main(string[] args) {
  // Warming up: let all the libraries (dll) be loaded, 
  // caches fed, prefetch (if any) done etc.
  for (int i = 0; i < 10; ++i) 
    methodUnderTest();

  // Now, let's run the test: just one execution more
  // if you have a leak, s1 and s2 will be different
  // since each run leads to leak of number of bytes
  string s1 = getMemoryStatusString();

  methodUnderTest();  

  string s2 = getMemoryStatusString();

  Console.Write(s1 + " -> " + s2);
}

And I see that memory before equals to memory after:

  (p: 59453440, v:662425600) -> (p: 59453440, v:662425600)
Insanitary answered 29/10, 2015 at 14:53 Comment(5)
You really should not be disposing fontFamily, you don't own the object, it is still a member of the Familes collection. Any future enumerations of that collection will possibly error out.Equalizer
@Scott Chamberlain: You're quite right, but this is the method under test from the question. I've edited the answer (put a comment)Insanitary
Yea, I just wanted to mention it because you added so many other comments to the code correcting other mistakes.Equalizer
@Scott Chamberlain: Thank you! I've really missed the issue with disposing in the method under test while making testing scaffoldings.Insanitary
I found this memory leak on Windows 10, i'm going to edit the title of the question.Cryptozoite
I
0

Calling dispose doesn't release manage memory straight away. It has to wait till GC.Collect occurs. If you want to measure memory after Disposing it you should forcefully execute GC.Collect and wait for finalization.

Execute forceful garbage collection as below, before you take after disposing memory reading and see.

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Inefficacious answered 30/10, 2015 at 11:15 Comment(1)
I already tried to force the GC but the behavior is the same.Cryptozoite

© 2022 - 2024 — McMap. All rights reserved.