Windows 10上的.NET FontFamily内存泄漏问题

4

在Windows 10上,System.Drawing.FontFamily.IsStyleAvailable方法似乎即使在调用Dispose方法后仍然会将分配的空间保留在内存中。

我编写了一个简单的控制台应用程序来测试它:

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();
        }
    }
}

有什么想法是为什么会发生这种情况吗?
提前致谢!

2
你真的不应该释放 fontFamily,因为你并不拥有这个对象,它仍然是 Families 集合的成员。对该集合的任何未来枚举都可能会出错。 - Scott Chamberlain
这根本不是Windows中内存管理的工作方式。释放的地址空间很少会被返还给操作系统。它会被添加回“空闲列表”,以供将来分配使用。不占用任何资源,只需为每4096字节保留一个数字。这是一种按需分页的虚拟内存操作系统的好处。只有当蓝色月亮进入宝瓶座象限并且空闲空间聚合成足够大的VM空间块时,才会被返回。 - Hans Passant
我只是为了突出问题而调用Dispose。 - Mirko Nosenzo
3个回答

0
如果存在内存泄漏,它可能在中,FontFamily.IsStyleAvailable()实际上会调用外部函数GdipIsStyleAvailable()
从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;
}

这又被定义为:

[DllImport("gdiplus.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
internal static extern int GdipIsStyleAvailable(HandleRef family, FontStyle style, out int isStyleAvailable);

0

我在我的工作站上看不到任何泄漏(我使用Windows 7.Net 4.5)。然而,在测试过程中存在一些问题。

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);
}

我看到内存之前等于内存之后:

  (p: 59453440, v:662425600) -> (p: 59453440, v:662425600)

1
你真的不应该释放 fontFamily,因为你并不拥有这个对象,它仍然是 Families 集合的成员。对该集合的任何未来枚举都可能会出错。 - Scott Chamberlain
@Scott Chamberlain:你说得很对,但这是问题中的测试方法。我已经编辑了答案(加了一条注释)。 - Dmitry Bychenko
1
是的,我只是想提一下,因为你在代码中添加了很多其他注释来纠正其他错误。 - Scott Chamberlain
@Scott Chamberlain:谢谢你!在制作测试框架时,我真的错过了清除使用中方法的问题。 - Dmitry Bychenko
我在Windows 10上发现了这个内存泄漏问题,我将编辑问题的标题。 - Mirko Nosenzo

0

调用dispose方法并不会立即释放托管内存。它必须等待GC.Collect的发生。如果您想在Dispose后测量内存,您应该强制执行GC.Collect并等待终结。

在进行内存释放读取之前,请按照以下方式执行强制垃圾回收并查看结果。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

我已经尝试强制垃圾回收,但行为仍然相同。 - Mirko Nosenzo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接