在C#监控垃圾收集器 [英] Monitoring Garbage Collector in C#

查看:138
本文介绍了在C#监控垃圾收集器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个WPF应用程序,它正在经历一个很大的性能问题。其中最糟糕的是,有时候应用程序只是冻结了几秒钟,然后再次运行前。

I have a WPF application that is experiencing a lot of performance issues. The worst of them is that sometimes the application just freezes for a few seconds before running again.

我目前正在调试应用程序,看看这个冻结可能与,我相信的东西,可能会导致它一个是垃圾收集器。由于我的应用程序是在一个非常有限的环境中运行,我认为,垃圾收集器可以使用所有的机器的资源时,它跑了,留下没有给我们的应用程序。

I am currently debugging the application to see what this freeze might be related to, and I believe that one of things that may be causing it is the Garbage Collector. Since my application is running in a very limited environment, I believe that the Garbage Collector can be using all of the machine's resources when it is ran and leaving none to our application.

要检查这个假设,我发现这些文章:垃圾回收通知和的Garbage集合通知在.NET 4.0中,解释如何我的应用程序时,可以通知该垃圾收集器将开始运行,当它完成。

To check this hypotheses I found these articles: Garbage Collection Notifications and Garbage Collection Notifications in .NET 4.0, that explain how my application can be notified when the Garbage Collector will begin running and when it is finished.

因此​​,基于这些文章中,我创建了下面的类来获得通知:

So, based on those articles I created the class below to get the notifications:

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文件(如下图):

And I've added the gcConcurrent option to my app.config file (below):

<?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的,因此内容。

However, whenever the application is executed, it seems as if no notification is sent that the Garbage Collector will run. I've put breakpoints in the DoGCMonitoring and it appears that the conditions (s == GCNotificationStatus.Succeeded) and (s == GCNotificationStatus.Succeeded) are never satisfied, therefore the contents of those ifs statements are never executed.

我是什么做错了吗?

注:我使用C#与WPF和.NET Framework 3.5

Note: I am using C# with WPF and the .NET Framework 3.5.

更新1

更新我的AllocationTest方法GCMonitor测试。此方法仅用于测试目的。我只是想确保有足够的内存已被分配给力垃圾收集器运行。

Updated my GCMonitor test with the AllocationTest method. This method is for testing purposes only. I just wanted to make sure that enough memory was being allocated to force the Garbage Collector to run.

更新2

上的方法WaitForFullGCApproach和WaitForFullGCComplete返回更新了DoGCMonitoring方法,新的检查。从我所看到的,到目前为止我的应用程序,直接到(S == GCNotificationStatus.NotApplicable)的条件。因此,我认为我有一些错误配置的地方,从获得所期望的结果阻止我。

Updated the DoGCMonitoring method, with new checks on the return of the methods WaitForFullGCApproach and WaitForFullGCComplete. From what I've seen so far my application is going directly to the (s == GCNotificationStatus.NotApplicable) condition. So I think that I have some misconfiguration somewhere that is stopping me from getting the desired results.

的说明文件GCNotificationStatus枚举可以发现<一href="http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US&k=k%28SYSTEM.GCNOTIFICATIONSTATUS%29;k%28GCNOTIFICATIONSTATUS%29;k%28TargetFrameworkMoniker-%22.NETFRAMEWORK,VERSION=V3.5%22%29;k%28DevLang-CSHARP%29&rd=true">here.

The documentation for the GCNotificationStatus enum can be found here.

推荐答案

我不明白<一个href="http://msdn.microsoft.com/en-us/library/system.gc.registerforfullgcnotification.aspx"><$c$c>GC.RegisterForFullGCNotification(int,int)在任何地方你code。它看起来像你使用 WaitForFullGC [XXX] 方法,但从来没有注册的通知。这可能是为什么你得到NotApplicable的状态。

I don't see GC.RegisterForFullGCNotification(int,int) anywhere in your code. It looks like you're using the WaitForFullGC[xxx] methods, but are never registering for the notification. That's probably why you're getting the NotApplicable status.

不过,我怀疑是GC是你的问题,虽然有可能,我想这将是很好的了解所有的GC模式有,并确定发生了什么事的最佳途径。有两种模式垃圾收集在.NET中的:服务器和工作站。他们都领取相同的未使用的内存,但是它的工作方式是非常轻微的不同。

However, I'm doubting that GC is your problem, while possible, I suppose it would be good to know about all of the GC modes there are and the best ways to determine what is happening. There are two modes of Garbage Collection in .NET: the Server and Workstation. They both collect the same unused memory, however the way it's done is ever so slightly different.

  • 服务器版本 - 此模式告诉GC为您使用的是服务器端的应用程序,并尝试优化集合了这些场景。它将堆分成几个部分,1元的CPU。当GC启动时,它会在平行每个CPU上运行一个线程。你真的想多CPU这个工作很好。当服务器版本使用多线程的GC,这是不一样的下面列出的并发工作站GC模式。每个线程的作用类似于非并发版本。

  • Server Version - This mode tells the GC for you're using a server side application, and it tries to optimize collections for these scenarios. It will split the heap into several sections, 1 per CPU. When the GC is started, it will run one thread on each CPU in parallel. You really want multiple CPUs for this to work well. While the server version uses multiple threads for the GC, it's not the same as the concurrent workstation GC mode listed below. Each thread acts like the non-concurrent version.

工作站版本 - 此模式告诉GC你使用一个客户端应用程序。它的数字你有更多有限的资源比服务器版本,所以只有一个GC线程。但是,也有的工作站版本两种配置:同时和非并发

Workstation Version - This mode tells GC you're using a client side application. It figures you have more limited resources than the Server version, and so there is only one GC thread. However, there are two configurations of the Workstation version: concurrent and non-concurrent.

  • 并行 - 这是版本默认打开的,只要该工作站GC使用(这将是您的WPF应用程序的情况下)。该GC总是在一个单独的线程,就是始终标记对象的集合中的应用程序运行时运行。此外,它选择是否压缩在一定世代的存储器,并基于性能这样的选择。它仍然有冻结所有的线程运行的集合,如果压实已完成,但使用此模式时,你将几乎从来没有看到一个响应的应用程​​序。这对于应用创建一个更好的交互体验,是最好的控制台或GUI应用程序。
  • 非并发 - 这是你可以配置你的应用程序使用,如果你想要一个版本。在这种模式下,GC线程休眠,直到GC启动,然后它并标记所有对象树是垃圾,释放内存,的压块,通通而所有其他线程被挂起。这可能会导致应用程序有时会停止响应了的的一段时间。
  • Concurrent - This is the version turned on by default whenever the workstation GC is used (this would be the case for your WPF application). The GC is always running on a separate thread that is always marking objects for collection when the application is running. Furthermore, it chooses whether or not to compact the memory in certain generations, and makes that choice based on performance. It still has to freeze all threads to run a collection if compacting is done, but you will almost never see an unresponsive application when using this mode. This creates a better interactive experience for uses and is best for console or GUI apps.
  • Non-Concurrent - This is a version you can configure your application to use, if you'd like. In this mode, the GC thread sleeps until a GC is started, then it goes and marks all object trees that are garbage, frees the memory, and compacts it, all while all other threads are suspended. This can cause application to sometimes become unresponsive for a short period of time.

您不能注册的并发收集器的通知,因为这是在后台完成。这有可能是您的应用程序不使用并发收集器(我注意到你有 gcConcurrent 在禁用的app.config ,但似乎这只是用于测试?)。如果是这样的话,你肯定能看到你的应用程序冻结,如果有沉重的集合。这就是为什么他们创造了并发收集器。 GC模式的类型可部分在code来设置,并在充分应用的结构和机构设置

You can't register for notifications on the concurrent collector, since that's done in the background. It's possible that your application is not using the concurrent collector (I notice you have the gcConcurrent disabled in the app.config, but it seems that's only for testing?). If that's the case, you can certainly see your application freezing if there's heavy collections. This is why they created the concurrent collector. The type of GC mode can partially be set in code, and fully set in application configurations and machine configurations.

我们可以做些什么来弄清楚到底是什么我们的应用程序正在使用?在运行时,可以查询静态 GCSettings 类(在 System.Runtime )。 GCSettings.IsServerGC 会告诉你,如果你正在运行的服务器版本和工作站<一href="http://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode.aspx"><$c$c>GCSettings.LatencyMode可以告诉你,如果你使用的同时,非并发或一个特别的你在code这是不是真的适用这里设置。我认为这是一个良好的开端,并可以解释为什么它是你的机器上运行良好,但不生产。

What can we do to figure out exactly what our application is using? At runtime, you can query the static GCSettings class (in System.Runtime). GCSettings.IsServerGC will tell you if you're running the workstation on server versions and GCSettings.LatencyMode can tell you if you're using the concurrent, non-concurrent or a special one you have to set in code which isn't really applicable here. I think that would be a good place to start, and could explain why it's running fine on your machine, but not production.

在配置文件中,&LT; gcConcurrent启用=真|假/&GT; &LT; gcServer启用=真|假/&GT; 控制垃圾收集器的模式。请记住,这可以在你的app.config文件(位于搁置执行的程序集)的的Machine.config文件,它位于%WINDIR%\微软。 NET \框架\ [版本] \ CONFIG \

In the configuration files, <gcConcurrent enabled="true|false"/> or <gcServer enabled="true|false"/> control the modes of the garbage collector. Keep in mind this can be in your app.config file (located aside the executing assembly) or in the machine.config file, which is located in %windir%\Microsoft.NET\Framework\[version]\CONFIG\

您还可以远程使用Windows性能监视器来访问生产机器的性能计数器.NET垃圾收集和查看这些统计数据。你可以做同样的与Windows事件跟踪(ETW)所有遥。对于性能监视器,你想要的 .NET CLR内存对象,并选择在实例列表框中选择的应用程序。

You can also remotely use the Windows Performance Monitor to access the production machine's performance counters for .NET garbage collection and view those statistics. You can do the same with Event Tracing for Windows (ETW) all remotely. For performance monitor, you'd want the .NET CLR Memory object, and select your application in the instances list box.

这篇关于在C#监控垃圾收集器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆