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