C# 在C语言中监控垃圾收集器#
我有一个WPF应用程序遇到了很多性能问题。最糟糕的情况是,有时应用程序在再次运行之前会冻结几秒钟 我目前正在调试该应用程序,以查看此冻结可能与什么有关,我相信可能导致此冻结的原因之一是垃圾收集器。由于我的应用程序运行在一个非常有限的环境中,我相信垃圾收集器可以在运行时使用机器的所有资源,而不留给我们的应用程序 为了验证这一假设,我找到了以下文章:这些文章解释了垃圾收集器何时开始运行以及何时完成如何通知我的应用程序 因此,基于这些文章,我创建了下面的类来获取通知:C# 在C语言中监控垃圾收集器#,c#,.net,.net-3.5,garbage-collection,C#,.net,.net 3.5,Garbage Collection,我有一个WPF应用程序遇到了很多性能问题。最糟糕的情况是,有时应用程序在再次运行之前会冻结几秒钟 我目前正在调试该应用程序,以查看此冻结可能与什么有关,我相信可能导致此冻结的原因之一是垃圾收集器。由于我的应用程序运行在一个非常有限的环境中,我相信垃圾收集器可以在运行时使用机器的所有资源,而不留给我们的应用程序 为了验证这一假设,我找到了以下文章:这些文章解释了垃圾收集器何时开始运行以及何时完成如何通知我的应用程序 因此,基于这些文章,我创建了下面的类来获取通知: public sealed cl
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();
}
}
GCMonitor公共密封类
{
私有静态监控实例;
私有静态对象syncRoot=新对象();
私有线程gcMonitorThread;
私有线程启动gcMonitorThreadStart;
私人住宅正在运行;
公共静态GCMonitor GetInstance()
{
if(实例==null)
{
锁定(同步根)
{
instance=new GCMonitor();
}
}
返回实例;
}
专用GCMonitor()
{
isRunning=false;
gcMonitorThreadStart=新线程开始(DoGCMonitoring);
gcMonitorThread=新线程(gcMonitorThreadStart);
}
公共无效开始监控()
{
如果(!正在运行)
{
gcMonitorThread.Start();
isRunning=true;
AllocationTest();
}
}
私有void DoGCMonitoring()
{
早在GC=0之前;
长后gc=0;
尝试
{
while(true)
{
//检查是否有接近集合的通知。
GCNotificationStatus s=GC.WaitForFullGCApproach(10000);
如果(s==GCNotificationStatus.successed)
{
//呼叫事件
beforeGC=GC.GetTotalMemory(false);
LogHelper.Log.InfoFormat(“==>GC我在您的代码中没有看到任何内容。看起来您正在使用WaitForFullGC[xxx]
方法,但从未注册通知。这可能是您获得不适用状态的原因
然而,我怀疑GC是否是你的问题,虽然可能,但我认为最好了解所有GC模式以及确定发生了什么的最佳方法。在.NET中有两种垃圾收集模式:服务器和工作站。它们都收集相同的未使用内存,不管它的方式如何完全不同
- 服务器版本-此模式告诉GC您正在使用服务器端应用程序,它会尝试优化这些场景的集合。它会将堆拆分为几个部分,每个CPU 1个。启动GC时,它会在每个CPU上并行运行一个线程。您真的希望多个CPU能够正常工作吗。虽然服务器版本为GC使用多个线程,但它与下面列出的并发工作站GC模式不同。每个线程的行为与非并发版本类似
- 工作站版本-此模式告诉GC您正在使用客户端应用程序。它表明您的资源比服务器版本更有限,因此只有一个GC线程。但是,工作站版本有两种配置:并发和非并发
- 并发-这是在使用工作站GC时默认打开的版本(WPF应用程序就是这种情况)。GC始终在一个单独的线程上运行,该线程在应用程序运行时始终标记要收集的对象。此外,它选择是否在某些代中压缩内存,并根据性能进行选择。如果压缩完成,它仍然必须冻结所有线程以运行收集,但您将使用此模式时,我几乎看不到无响应的应用程序。这为用户创造了更好的交互体验,最适合于控制台或GUI应用程序
- 非并发-这是一个您可以配置应用程序以使用的版本,如果您愿意。在这种模式下,GC线程在启动GC之前一直处于休眠状态,然后在所有其他线程挂起的同时,它会去标记所有垃圾对象树,释放内存并压缩它。这可能会导致应用程序有时停止在短时间内没有反应
您无法在并发收集器上注册通知,因为这是在后台完成的。您的应用程序可能没有使用并发收集器(我注意到您在app.config
中禁用了gcConcurrent
,但这似乎仅用于测试?)。如果是这种情况,如果存在大量收集,您肯定会看到应用程序冻结。这就是他们创建并发收集器的原因。GC模式的类型可以在代码中部分设置,在应用程序配置和计算机配置中完全设置
我们可以做些什么来准确了解我们的应用程序正在使用什么?在运行时,您可以查询静态GCSettings
类(在System.runtime
).GCSettings.IsServerGC
将告诉您是否在服务器版本上运行工作站,并可以告诉您使用的是并发、非并发还是必须在代码中设置的特殊版本,这些代码在这里并不适用。我认为这是一个很好的起点,并可以解释为什么它在您的机器上运行良好,但不是生产
在配置文件中,<?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>