.NET FontFamily memory leak on Windows 10

2019-06-26 05:33发布

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!

3条回答
\"骚年 ilove
2楼-- · 2019-06-26 06:03

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();
查看更多
一夜七次
3楼-- · 2019-06-26 06:06

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);
查看更多
手持菜刀,她持情操
4楼-- · 2019-06-26 06:23

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)
查看更多
登录 后发表回答