我有一个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