我有一個WPF應用程序遇到很多性能問題。最糟糕的是,有時候應用程序會在再次運行之前凍結幾秒鐘。在C中監視垃圾收集器#
我目前正在調試應用程序,看看這個凍結可能與什麼有關,我相信可能導致它的一件事是垃圾收集器。由於我的應用程序運行在非常有限的環境中,因此我相信垃圾收集器可以在運行時使用機器的所有資源,並且不會對我們的應用程序造成任何影響。
爲了檢查這個假設,我發現了這些文章:Garbage Collection Notifications和Garbage Collection Notifications in .NET 4.0,它解釋了當垃圾收集器開始運行和完成時應如何通知我的應用程序。
所以,基於這些文章中,我創建了下面的類來獲得通知:
public sealed class GCMonitor
{
private static volatile GCMonitor instance;
private static object syncRoot = new object();
private Thread gcMonitorThread;
private ThreadStart gcMonitorThreadStart;
private bool isRunning;
public static GCMonitor GetInstance()
{
if (instance == null)
{
lock (syncRoot)
{
instance = new GCMonitor();
}
}
return instance;
}
private GCMonitor()
{
isRunning = false;
gcMonitorThreadStart = new ThreadStart(DoGCMonitoring);
gcMonitorThread = new Thread(gcMonitorThreadStart);
}
public void StartGCMonitoring()
{
if (!isRunning)
{
gcMonitorThread.Start();
isRunning = true;
AllocationTest();
}
}
private void DoGCMonitoring()
{
long beforeGC = 0;
long afterGC = 0;
try
{
while (true)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach(10000);
if (s == GCNotificationStatus.Succeeded)
{
//Call event
beforeGC = GC.GetTotalMemory(false);
LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC is about to begin. Memory before GC: %d", beforeGC);
GC.Collect();
}
else if (s == GCNotificationStatus.Canceled)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was cancelled");
}
else if (s == GCNotificationStatus.Timeout)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was timeout");
}
else if (s == GCNotificationStatus.NotApplicable)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was not applicable");
}
else if (s == GCNotificationStatus.Failed)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event failed");
}
// Check for a notification of a completed collection.
s = GC.WaitForFullGCComplete(10000);
if (s == GCNotificationStatus.Succeeded)
{
//Call event
afterGC = GC.GetTotalMemory(false);
LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC has ended. Memory after GC: %d", afterGC);
long diff = beforeGC - afterGC;
if (diff > 0)
{
LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "Collected memory: %d", diff);
}
}
else if (s == GCNotificationStatus.Canceled)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was cancelled");
}
else if (s == GCNotificationStatus.Timeout)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was timeout");
}
else if (s == GCNotificationStatus.NotApplicable)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was not applicable");
}
else if (s == GCNotificationStatus.Failed)
{
LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event failed");
}
Thread.Sleep(1500);
}
}
catch (Exception e)
{
LogHelper.Log.Error(" ******************** Garbage Collector Error ************************ ");
LogHelper.LogAllErrorExceptions(e);
LogHelper.Log.Error(" ------------------- Garbage Collector Error --------------------- ");
}
}
private void AllocationTest()
{
// Start a thread using WaitForFullGCProc.
Thread stress = new Thread(() =>
{
while (true)
{
List<char[]> lst = new List<char[]>();
try
{
for (int i = 0; i <= 30; i++)
{
char[] bbb = new char[900000]; // creates a block of 1000 characters
lst.Add(bbb); // Adding to list ensures that the object doesnt gets out of scope
}
Thread.Sleep(1000);
}
catch (Exception ex)
{
LogHelper.Log.Error(" ******************** Garbage Collector Error ************************ ");
LogHelper.LogAllErrorExceptions(e);
LogHelper.Log.Error(" ------------------- Garbage Collector Error --------------------- ");
}
}
});
stress.Start();
}
}
而且我已經添加了gcConcurrent選項,以我的app.config文件(如下圖):
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net-net-2.0"/>
</configSections>
<runtime>
<gcConcurrent enabled="false" />
</runtime>
<log4net>
<appender name="Root.ALL" type="log4net.Appender.RollingFileAppender">
<param name="File" value="../Logs/Root.All.log"/>
<param name="AppendToFile" value="true"/>
<param name="MaxSizeRollBackups" value="10"/>
<param name="MaximumFileSize" value="8388608"/>
<param name="RollingStyle" value="Size"/>
<param name="StaticLogFileName" value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%date [%thread] %-5level - %message%newline"/>
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="Root.ALL"/>
</root>
</log4net>
<appSettings>
<add key="setting1" value="1"/>
<add key="setting2" value="2"/>
</appSettings>
<startup>
<supportedRuntime version="v2.0.50727"/>
</startup>
</configuration>
但是,無論何時執行應用程序,看起來好像沒有發送垃圾收集器將運行的通知。我在DoGCMonitoring中放置了斷點,看起來條件(s == GCNotificationStatus.Succeeded)和(s == GCNotificationStatus.Succeeded)永遠不會被滿足,因此這些ifs語句的內容永遠不會被執行。
我在做什麼錯?
注:我正在使用C#與WPF和.NET Framework 3.5。
更新1
更新我GCMonitor測試與AllocationTest方法。此方法僅用於測試目的。我只是想確保分配足夠的內存來強制垃圾收集器運行。
UPDATE 2
更新了DoGCMonitoring方法,對所述方法和WaitForFullGCApproach WaitForFullGCComplete返回新的檢查。從我目前看到的應用程序直接進入(s == GCNotificationStatus.NotApplicable)條件。所以我認爲我在某個地方有一些錯誤配置,導致我無法獲得理想的結果。
GCNotificationStatus枚舉的文檔可以找到here。
您是否嘗試過使用工具實際分析它,比如說Windows性能監視器或windbg--而不是嘗試寫一個GC包裝器? – 2012-03-12 15:53:02
也許GC未運行(尚未)。你能顯示AllocationTest()嗎? – 2012-03-12 15:55:31
嗨,我其實有一個分析工具,但是我之前提到的凍結問題發生在生產環境中,而不是在我的機器上(我無法再現它)。不幸的是,對我來說,我無法在生產環境中運行分析工具。 – Felipe 2012-03-12 15:59:31