我有一个WPF应用程序,遇到很多性能问题。最糟糕的是,有时应用程序会冻结几秒钟,然后再次运行。
我目前正在调试该应用程序,以查看此冻结可能与之相关,并且我认为可能导致此冻结的原因之一是垃圾收集器。由于我的应用程序在非常有限的环境中运行,因此我相信垃圾收集器在运行时可以使用机器的所有资源,而不会将任何资源留给我们的应用程序。
为了验证这一假设,我找到了以下文章: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语句的内容永远不会执行。
我究竟做错了什么?
注意:我在WPF和.NET Framework 3.5中使用C#。
更新1
使用AllocationTest方法更新了我的GCMonitor测试。此方法仅用于测试目的。我只是想确保分配了足够的内存来强制垃圾回收器运行。
更新2
更新了DoGCMonitoring方法,并对方法WaitForFullGCApproach和WaitForFullGCComplete的返回进行了新检查。从目前为止我所看到的,我的应用程序将直接进入(s == GCNotificationStatus.NotApplicable)条件。因此,我认为我在某处存在一些配置错误,这使我无法获得所需的结果。
可以在here中找到GCNotificationStatus枚举的文档。
最佳答案
在您的代码中的任何地方都看不到 GC.RegisterForFullGCNotification(int,int)
。看起来您正在使用WaitForFullGC[xxx]
方法,但从未注册该通知。这可能就是为什么您获得“不适用”状态的原因。
但是,我怀疑GC是您的问题,尽管可能,但我想最好了解其中存在的所有GC模式以及确定正在发生的最佳方法。 .NET中有两种垃圾收集模式:服务器和工作站。它们都收集相同的未使用内存,但是其完成方式却稍有不同。
您不能在并发收集器上注册通知,因为这是在后台完成的。您的应用程序可能没有使用并发收集器(我注意到您在
gcConcurrent
中禁用了app.config
,但是似乎仅用于测试吗?)。如果是这种情况,那么在有大量收集的情况下,您当然可以看到应用程序冻结。这就是为什么他们创建并发收集器的原因。 GC模式的类型可以在代码中部分设置,而在应用程序配置和机器配置中完全设置。我们该怎么做才能确切地了解我们的应用程序正在使用什么?在运行时,您可以查询静态
GCSettings
类(在System.Runtime
中)。 GCSettings.IsServerGC
会告诉您您是否在服务器版本上运行工作站,而 GCSettings.LatencyMode
可以告诉您是否正在使用并发,非并发或特殊的代码,您必须在此处设置的代码并不适用。我认为这将是一个不错的起点,并可以解释为什么它在您的机器上运行正常,但在生产环境上却无法正常运行。在配置文件中,
<gcConcurrent enabled="true|false"/>
或<gcServer enabled="true|false"/>
控制垃圾收集器的模式。请记住,这可以在您的app.config文件中(位于执行程序集旁边)或在%windir%\Microsoft.NET\Framework\[version]\CONFIG\
中的machine.config文件中您还可以远程使用Windows性能监视器来访问生产计算机的.NET垃圾收集性能计数器,并查看这些统计信息。您可以对Windows事件跟踪(ETW)进行全部远程操作。对于性能监视器,您需要
.NET CLR Memory
对象,然后在实例列表框中选择您的应用程序。
关于c# - 在C#中监视垃圾收集器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9669963/