垃圾回收(三)【垃圾回收通知】

風靈使發表於2019-01-08

在有些情況下,公共語言執行時執行的完整垃圾回收(即第 2 代回收)可能會對效能產生負面影響。 特別是,處理大量請求的伺服器可能會出現此問題;在這種情況下,長時間垃圾回收會導致請求超時。為了防止在關鍵時期發生完全回收,可以接收即將執行完全垃圾回收的通知,再採取措施將工作負載重定向到另一個伺服器例項。 也可以自行誘導回收,前提是當前伺服器例項不需要處理請求。

RegisterForFullGCNotification 方法註冊為,在執行時檢測到即將執行完全垃圾回收時發出通知。 此通知分為兩個部分:完全垃圾回收何時即將執行,以及完全垃圾回收何時完成。

警告
只有阻止垃圾回收會引發通知。 如果 <gcConcurrent> 配置元素已啟用,後臺垃圾回收不會發出通知。

若要確定何時發出通知,請使用 WaitForFullGCApproachWaitForFullGCComplete 方法。 通常,在 while 迴圈中使用這些方法,以持續獲取表示通知狀態的 GCNotificationStatus 列舉。 如果值為 Succeeded,可以執行下列操作:

  • 為了響應使用 WaitForFullGCApproach 方法獲得的通知,可以重定向工作負載,並能自行誘導回收。
  • 為了響應使用 WaitForFullGCComplete 方法獲得的通知,可以讓當前伺服器例項再次用於處理請求。 也可以收集資訊。
    例如,可以使用 CollectionCount 方法記錄回收次數。

WaitForFullGCApproachWaitForFullGCComplete 方法要配合使用。 使用一個方法,而不使用另一個方法,可能會生成不確定的結果。

完全垃圾回收

如果發生下列任一情況,執行時就會執行完全垃圾回收:

  • 足夠多的記憶體已提升到第 2 代,導致執行下一個第 2 代回收。
  • 足夠多的記憶體已提升到大型物件堆,導致執行下一個第 2 代回收。
  • 由於其他因素,導致第 1 代回收升級為第 2 代回收。

RegisterForFullGCNotification 方法中指定的閾值適用於前兩種情況。 不過,在第一種情況下,不一定會在與指定的閾值相稱的時間收到通知,原因有下面兩個:

  • 執行時不檢查每個小型物件分配(出於效能考慮)。
  • 只有第 1 代回收將記憶體提升到第 2 代。

第三種情況也加劇了通知接收時間的不確定性。 可以在此期間重定向請求,或在可以更好適應時自行誘導回收,從而減輕不合時宜的完全垃圾回收造成的影響。儘管並不保證有效,但確實證明這是非常實用的方法。

通知閾值引數

RegisterForFullGCNotification 方法包含兩個引數,用於指定第 2 代物件和大型物件堆的閾值。 如果達到這些值,就應發出垃圾回收通知。 下表介紹了這些引數。

引數 描述
maxGenerationThreshold 介於 1 和 99 之間的數字,指定根據在第 2 代中提升的物件,應何時發出通知。
largeObjectHeapThreshold 介於 1 和 99 之間的數字,指定根據大型物件堆中分配的物件,應何時發出通知。

如果指定的值過高,很可能會出現的情況是,將會收到通知,但在執行時執行回收前等待的時間太長。 如果自行誘導回收,回收的物件可能會多於在執行時執行回收時回收的物件。

如果指定的值過低,在執行時執行回收前等待通知的時間可能不夠長。

示例

描述

在下面的示例中,一組伺服器處理傳入的 Web 請求。 為了模擬處理請求的工作負載,將位元組陣列新增到 List<T> 集合中。 每個伺服器都會註冊獲取垃圾回收通知,再對 WaitForFullGCProc 使用者方法啟動執行緒,以持續監視 WaitForFullGCApproachWaitForFullGCComplete 方法返回的 GCNotificationStatus 列舉。

在通知發出時,WaitForFullGCApproachWaitForFullGCComplete 方法呼叫它們各自的事件處理使用者方法:

  • OnFullGCApproachNotify
    此方法呼叫 RedirectRequests 使用者方法,指示請求佇列伺服器暫停向伺服器傳送請求。 具體模擬方式是,將類級別變數 bAllocate 設定為 false,這樣就不會再分配物件。
    接下來,呼叫 FinishExistingRequests 使用者方法,完成處理掛起的伺服器請求。 具體模擬方式是,清除 List<T> 集合。
    最後,由於工作負載很輕,誘導垃圾回收。
  • OnFullGCCompleteNotify
    此方法呼叫使用者方法 AcceptRequests 以繼續接受請求,因為伺服器不再易受完全垃圾回收影響。 此操作的具體模擬方式是,將 bAllocate 變數設定為 true,以便能夠繼續將物件新增到 List<T> 集合。

下面的程式碼包含示例的 Main 方法。


using System;
using System.Collections.Generic;
using System.Threading;

namespace GCNotify
{
    class Program
    {
        // Variable for continual checking in the 
        // While loop in the WaitForFullGCProc method.
        static bool checkForNotify = false;

        // Variable for suspending work 
        // (such servicing allocated server requests)
        // after a notification is received and then 
        // resuming allocation after inducing a garbage collection.
        static bool bAllocate = false;

        // Variable for ending the example.
        static bool finalExit = false;

        // Collection for objects that  
        // simulate the server request workload.
        static List<byte[]> load = new List<byte[]>();


        public static void Main(string[] args)
        {
            try
            {
                // Register for a notification. 
                GC.RegisterForFullGCNotification(10, 10);
                Console.WriteLine("Registered for GC notification.");

                checkForNotify = true;
                bAllocate = true;

                // Start a thread using WaitForFullGCProc.
                Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
                thWaitForFullGC.Start();

                // While the thread is checking for notifications in
                // WaitForFullGCProc, create objects to simulate a server workload.
                try
                {

                    int lastCollCount = 0;
                    int newCollCount = 0;
                    
                    
                    while (true)
                    {
                        if (bAllocate)
                        {
                            load.Add(new byte[1000]);
                            newCollCount = GC.CollectionCount(2);
                            if (newCollCount != lastCollCount)
                            {
                                // Show collection count when it increases:
                                Console.WriteLine("Gen 2 collection count: {0}", GC.CollectionCount(2).ToString());
                                lastCollCount = newCollCount;
                            }
                           
                            // For ending the example (arbitrary).
                            if (newCollCount == 500)
                            {
                                finalExit = true;
                                checkForNotify = false;
                                break;
                            }
                        }
                    }

                }
                catch (OutOfMemoryException)
                {
                    Console.WriteLine("Out of memory.");
                }


                finalExit = true;
                checkForNotify = false;
                GC.CancelFullGCNotification();

            }
            catch (InvalidOperationException invalidOp)
            {

                Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"
                    + invalidOp.Message);
            }
        }

        public static void OnFullGCApproachNotify()
        {

            Console.WriteLine("Redirecting requests.");

            // Method that tells the request queuing  
            // server to not direct requests to this server. 
            RedirectRequests();

            // Method that provides time to 
            // finish processing pending requests. 
            FinishExistingRequests();

            // This is a good time to induce a GC collection
            // because the runtime will induce a full GC soon.
            // To be very careful, you can check precede with a
            // check of the GC.GCCollectionCount to make sure
            // a full GC did not already occur since last notified.
            GC.Collect();
            Console.WriteLine("Induced a collection.");

        }


        public static void OnFullGCCompleteEndNotify()
        {
            // Method that informs the request queuing server
            // that this server is ready to accept requests again.
            AcceptRequests();
            Console.WriteLine("Accepting requests again.");
        }

        public static void WaitForFullGCProc()
        {
            while (true)
            {
                // CheckForNotify is set to true and false in Main.
                while (checkForNotify)
                {
                    // Check for a notification of an approaching collection.
                    GCNotificationStatus s = GC.WaitForFullGCApproach();
                    if (s == GCNotificationStatus.Succeeded)
                    {
                        Console.WriteLine("GC Notification raised.");
                        OnFullGCApproachNotify();
                    }
                    else if (s == GCNotificationStatus.Canceled)
                    {
                        Console.WriteLine("GC Notification cancelled.");
                        break;
                    }
                    else
                    {
                        // This can occur if a timeout period
                        // is specified for WaitForFullGCApproach(Timeout) 
                        // or WaitForFullGCComplete(Timeout)  
                        // and the time out period has elapsed. 
                        Console.WriteLine("GC Notification not applicable.");
                        break;
                    }

                    // Check for a notification of a completed collection.
                    GCNotificationStatus status = GC.WaitForFullGCComplete();
                    if (status == GCNotificationStatus.Succeeded)
                    {
                        Console.WriteLine("GC Notification raised.");
                        OnFullGCCompleteEndNotify();
                    }
                    else if (status == GCNotificationStatus.Canceled)
                    {
                        Console.WriteLine("GC Notification cancelled.");
                        break;
                    }
                    else
                    {
                        // Could be a time out.
                        Console.WriteLine("GC Notification not applicable.");
                        break;
                    }
                }


                Thread.Sleep(500);
                // FinalExit is set to true right before  
                // the main thread cancelled notification.
                if (finalExit)
                {
                    break;
                }
            }

        }

        private static void RedirectRequests()
        {
            // Code that sends requests
            // to other servers.

            // Suspend work.
            bAllocate = false;

        }

        private static void FinishExistingRequests()
        {
            // Code that waits a period of time
            // for pending requests to finish.

            // Clear the simulated workload.
            load.Clear();

        }

        private static void AcceptRequests()
        {
            // Code that resumes processing
            // requests on this server.

            // Resume work.
            bAllocate = true;

        }
    }
}

下面的程式碼包含 WaitForFullGCProc 使用者方法,其中包括持續 while 迴圈,用於檢查是否有垃圾回收通知。


public static void WaitForFullGCProc()
{
    while (true)
    {
        // CheckForNotify is set to true and false in Main.
        while (checkForNotify)
        {
            // Check for a notification of an approaching collection.
            GCNotificationStatus s = GC.WaitForFullGCApproach();
            if (s == GCNotificationStatus.Succeeded)
            {
                Console.WriteLine("GC Notification raised.");
                OnFullGCApproachNotify();
            }
            else if (s == GCNotificationStatus.Canceled)
            {
                Console.WriteLine("GC Notification cancelled.");
                break;
            }
            else
            {
                // This can occur if a timeout period
                // is specified for WaitForFullGCApproach(Timeout) 
                // or WaitForFullGCComplete(Timeout)  
                // and the time out period has elapsed. 
                Console.WriteLine("GC Notification not applicable.");
                break;
            }

            // Check for a notification of a completed collection.
            GCNotificationStatus status = GC.WaitForFullGCComplete();
            if (status == GCNotificationStatus.Succeeded)
            {
                Console.WriteLine("GC Notification raised.");
                OnFullGCCompleteEndNotify();
            }
            else if (status == GCNotificationStatus.Canceled)
            {
                Console.WriteLine("GC Notification cancelled.");
                break;
            }
            else
            {
                // Could be a time out.
                Console.WriteLine("GC Notification not applicable.");
                break;
            }
        }


        Thread.Sleep(500);
        // FinalExit is set to true right before  
        // the main thread cancelled notification.
        if (finalExit)
        {
            break;
        }
    }
    
}

下面的程式碼包含 OnFullGCApproachNotify 方法,呼叫自WaitForFullGCProc 方法。

public static void OnFullGCApproachNotify()
{

    Console.WriteLine("Redirecting requests.");

    // Method that tells the request queuing  
    // server to not direct requests to this server. 
    RedirectRequests();

    // Method that provides time to 
    // finish processing pending requests. 
    FinishExistingRequests();

    // This is a good time to induce a GC collection
    // because the runtime will induce a full GC soon.
    // To be very careful, you can check precede with a
    // check of the GC.GCCollectionCount to make sure
    // a full GC did not already occur since last notified.
    GC.Collect();
    Console.WriteLine("Induced a collection.");

}

下面的程式碼包含 OnFullGCApproachComplete 方法,呼叫自WaitForFullGCProc 方法。

public static void OnFullGCCompleteEndNotify()
{
    // Method that informs the request queuing server
    // that this server is ready to accept requests again.
    AcceptRequests();
    Console.WriteLine("Accepting requests again.");
}

下面的程式碼包含呼叫自 OnFullGCApproachNotifyOnFullGCCompleteNotify 方法的使用者方法。 使用者方法重定向請求,完成現有請求,再在發生完全垃圾回收後繼續執行請求。

private static void RedirectRequests()
{
    // Code that sends requests
    // to other servers.

    // Suspend work.
    bAllocate = false;

}

private static void FinishExistingRequests()
{
    // Code that waits a period of time
    // for pending requests to finish.

    // Clear the simulated workload.
    load.Clear();

}

private static void AcceptRequests()
{
    // Code that resumes processing
    // requests on this server.

    // Resume work.
    bAllocate = true;

}

整個程式碼示例如下所示:


using System;
using System.Collections.Generic;
using System.Threading;

namespace GCNotify
{
    class Program
    {
        // Variable for continual checking in the 
        // While loop in the WaitForFullGCProc method.
        static bool checkForNotify = false;

        // Variable for suspending work 
        // (such servicing allocated server requests)
        // after a notification is received and then 
        // resuming allocation after inducing a garbage collection.
        static bool bAllocate = false;

        // Variable for ending the example.
        static bool finalExit = false;

        // Collection for objects that  
        // simulate the server request workload.
        static List<byte[]> load = new List<byte[]>();


        public static void Main(string[] args)
        {
            try
            {
                // Register for a notification. 
                GC.RegisterForFullGCNotification(10, 10);
                Console.WriteLine("Registered for GC notification.");

                checkForNotify = true;
                bAllocate = true;

                // Start a thread using WaitForFullGCProc.
                Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
                thWaitForFullGC.Start();

                // While the thread is checking for notifications in
                // WaitForFullGCProc, create objects to simulate a server workload.
                try
                {

                    int lastCollCount = 0;
                    int newCollCount = 0;
                    
                    
                    while (true)
                    {
                        if (bAllocate)
                        {
                            load.Add(new byte[1000]);
                            newCollCount = GC.CollectionCount(2);
                            if (newCollCount != lastCollCount)
                            {
                                // Show collection count when it increases:
                                Console.WriteLine("Gen 2 collection count: {0}", GC.CollectionCount(2).ToString());
                                lastCollCount = newCollCount;
                            }
                           
                            // For ending the example (arbitrary).
                            if (newCollCount == 500)
                            {
                                finalExit = true;
                                checkForNotify = false;
                                break;
                            }
                        }
                    }

                }
                catch (OutOfMemoryException)
                {
                    Console.WriteLine("Out of memory.");
                }


                finalExit = true;
                checkForNotify = false;
                GC.CancelFullGCNotification();

            }
            catch (InvalidOperationException invalidOp)
            {

                Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"
                    + invalidOp.Message);
            }
        }

        public static void OnFullGCApproachNotify()
        {

            Console.WriteLine("Redirecting requests.");

            // Method that tells the request queuing  
            // server to not direct requests to this server. 
            RedirectRequests();

            // Method that provides time to 
            // finish processing pending requests. 
            FinishExistingRequests();

            // This is a good time to induce a GC collection
            // because the runtime will induce a full GC soon.
            // To be very careful, you can check precede with a
            // check of the GC.GCCollectionCount to make sure
            // a full GC did not already occur since last notified.
            GC.Collect();
            Console.WriteLine("Induced a collection.");

        }


        public static void OnFullGCCompleteEndNotify()
        {
            // Method that informs the request queuing server
            // that this server is ready to accept requests again.
            AcceptRequests();
            Console.WriteLine("Accepting requests again.");
        }

        public static void WaitForFullGCProc()
        {
            while (true)
            {
                // CheckForNotify is set to true and false in Main.
                while (checkForNotify)
                {
                    // Check for a notification of an approaching collection.
                    GCNotificationStatus s = GC.WaitForFullGCApproach();
                    if (s == GCNotificationStatus.Succeeded)
                    {
                        Console.WriteLine("GC Notification raised.");
                        OnFullGCApproachNotify();
                    }
                    else if (s == GCNotificationStatus.Canceled)
                    {
                        Console.WriteLine("GC Notification cancelled.");
                        break;
                    }
                    else
                    {
                        // This can occur if a timeout period
                        // is specified for WaitForFullGCApproach(Timeout) 
                        // or WaitForFullGCComplete(Timeout)  
                        // and the time out period has elapsed. 
                        Console.WriteLine("GC Notification not applicable.");
                        break;
                    }

                    // Check for a notification of a completed collection.
                    GCNotificationStatus status = GC.WaitForFullGCComplete();
                    if (status == GCNotificationStatus.Succeeded)
                    {
                        Console.WriteLine("GC Notification raised.");
                        OnFullGCCompleteEndNotify();
                    }
                    else if (status == GCNotificationStatus.Canceled)
                    {
                        Console.WriteLine("GC Notification cancelled.");
                        break;
                    }
                    else
                    {
                        // Could be a time out.
                        Console.WriteLine("GC Notification not applicable.");
                        break;
                    }
                }


                Thread.Sleep(500);
                // FinalExit is set to true right before  
                // the main thread cancelled notification.
                if (finalExit)
                {
                    break;
                }
            }

        }

        private static void RedirectRequests()
        {
            // Code that sends requests
            // to other servers.

            // Suspend work.
            bAllocate = false;

        }

        private static void FinishExistingRequests()
        {
            // Code that waits a period of time
            // for pending requests to finish.

            // Clear the simulated workload.
            load.Clear();

        }

        private static void AcceptRequests()
        {
            // Code that resumes processing
            // requests on this server.

            // Resume work.
            bAllocate = true;

        }
    }
}

相關文章