Leggere in inglese

Condividi tramite


Notifiche di Garbage Collection

In alcune situazioni un'operazione completa di Garbage Collection (cioè di generazione 2) eseguita da Common Language Runtime può influire negativamente sulle prestazioni. Può essere un problema soprattutto con i server che elaborano grandi volumi di richieste. In questo caso, una lunga procedura di Garbage Collection può provocare un timeout delle richieste. Per evitare che venga eseguita una raccolta completa durante un periodo critico, è possibile essere informati che sta per iniziare una Garbage Collection completa e quindi intraprendere un'azione per reindirizzare il carico di lavoro a un'altra istanza del server. È anche possibile indurre manualmente una raccolta, a condizione che l'istanza del server corrente non sia necessaria per elaborare le richieste.

Il metodo RegisterForFullGCNotification registra la generazione di una notifica quando il runtime rileva che sta per essere eseguita una Garbage Collection completa. Esistono due parti per questa notifica: quando sta per essere eseguita la Garbage Collection completa e quando la Garbage Collection completa è terminata.

Avviso

Quando l'elemento di configurazione <gcConcurrent> è abilitato, WaitForFullGCComplete può restituire NotApplicable GCNotificationStatus se il processo GC completo è stato eseguito come GC in background.

Per determinare quando è stata generata una notifica, usare i metodi WaitForFullGCApproach e WaitForFullGCComplete. Questi metodi vengono in genere usati in un ciclo while per ottenere continuamente un'enumerazione GCNotificationStatus che indica lo stato della notifica. Se tale valore è Succeeded, è possibile procedere nel modo seguente:

  • In risposta a una notifica ottenuta con il metodo WaitForFullGCApproach, è possibile reindirizzare il carico di lavoro e probabilmente indurre manualmente una raccolta.

  • In risposta a una notifica ottenuta con il metodo WaitForFullGCComplete, è possibile rendere l'istanza corrente del server disponibile per elaborare di nuovo le richieste. È anche possibile raccogliere informazioni. È ad esempio è possibile usare il metodo CollectionCount per registrare il numero di raccolte.

I metodi WaitForFullGCApproach e WaitForFullGCComplete sono progettati per interagire. L'uso di uno senza l'altro può produrre risultati imprevisti.

Garbage Collection completa

Il runtime genera una Garbage Collection completa quando si verifica uno degli scenari seguenti:

  • È stata promossa memoria sufficiente alla generazione 2 per generare la raccolta di generazione 2 successiva.

  • È stata promossa memoria sufficiente all'heap degli oggetti grandi per generare la raccolta di generazione 2 successiva.

  • Viene eseguita l'escalation di una raccolta di generazione 1 a una raccolta di generazione 2 a causa di altri fattori.

Le soglie specificate nel metodo RegisterForFullGCNotification si applicano ai primi due scenari. Nel primo scenario, tuttavia, non si riceverà sempre la notifica nel periodo di tempo proporzionale ai valori della soglia specificati, per due motivi:

  • Il runtime non controlla ogni allocazione di oggetti piccoli (per motivi di prestazioni).

  • Sole le raccolte di generazione 1 promuovono la memoria alla generazione 2.

Nel terzo scenario è incerto anche quando si riceverà la notifica. Anche se non è una garanzia, è un modo utile per ridurre gli effetti di una Garbage Collection completa non opportuna reindirizzando le richieste durante questo intervallo di tempo o inducendo manualmente la raccolta quando può essere gestita meglio.

Parametri delle soglie di notifica

Il metodo RegisterForFullGCNotification presenta due parametri per specificare i valori di soglia degli oggetti di generazione 2 e dell'heap degli oggetti grandi. Quando tali valori sono soddisfatti, verrà generata una notifica di Garbage Collection. Nella tabella seguente vengono descritti i parametri.

Parametro Descrizione
maxGenerationThreshold Numero compreso tra 1 e 99 che specifica se la notifica deve essere generata in base agli oggetti promossi alla generazione 2.
largeObjectHeapThreshold Numero compreso tra 1 e 99 che specifica quando generare la notifica in base agli oggetti allocati nell'heap degli oggetti grandi.

Se si specifica un valore troppo elevato, è molto probabile che si riceverà una notifica, ma il tempo di attesa prima che il runtime causi una raccolta potrebbe essere troppo lungo. Se si induce manualmente una raccolta, potrebbero essere recuperati più oggetti di quanti ne verrebbero recuperati se fosse il runtime a causare la raccolta.

Se si specifica un valore troppo basso, il runtime potrebbe causare la raccolta prima di aver avuto tempo sufficiente per ricevere la notifica.

Esempio

Descrizione

Nell'esempio seguente un gruppo di server gestisce le richieste Web in ingresso. Per simulare il carico di lavoro dell'elaborazione delle richieste, vengono aggiunte matrici di byte a una raccolta List<T>. Ogni server registra una notifica di Garbage Collection e quindi avvia un thread nel metodo utente WaitForFullGCProc per monitorare costantemente l'enumerazione GCNotificationStatus restituita dai metodi WaitForFullGCApproach e WaitForFullGCComplete.

I metodi WaitForFullGCApproach e WaitForFullGCComplete chiamano i rispettivi metodi utente di gestione eventi quando viene generata una notifica:

  • OnFullGCApproachNotify

    Questo metodo chiama il metodo utente RedirectRequests, che indica al server di accodamento delle richieste di sospendere l'invio di richieste al server. Questa azione viene simulata impostando la variabile a livello di classe bAllocate su false in modo che non vengano allocati altri oggetti.

    Viene quindi chiamato il metodo utente FinishExistingRequests per completare l'elaborazione delle richieste al server in sospeso. Questa azione viene simulata cancellando la raccolta List<T>.

    Viene infine indotta una Garbage Collection perché il carico di lavoro è leggero.

  • OnFullGCCompleteNotify

    Questo metodo chiama il metodo utente AcceptRequests per riprendere l'accettazione delle richieste perché il server non è più soggetto a una Garbage Collection completa. Questa azione viene simulata impostando la variabile bAllocate su true in modo che sia possibile riprendere l'aggiunta degli oggetti alla raccolta List<T>.

Il codice seguente contiene il metodo Main dell'esempio.

C#
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);
    }
}

Il codice seguente contiene il metodo utente WaitForFullGCProc, che include un ciclo while continuo per cercare le notifiche di Garbage Collection.

C#
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;
        }
    }
}

Il codice seguente contiene il metodo OnFullGCApproachNotify chiamato dal

MetodoWaitForFullGCProc .

C#
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.");
}

Il codice seguente contiene il metodo OnFullGCApproachComplete chiamato dal

MetodoWaitForFullGCProc .

C#
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.");
}

Il codice seguente contiene i metodi utente chiamati dai metodi OnFullGCApproachNotify e OnFullGCCompleteNotify. I metodi utente reindirizzano le richieste, completano le richieste esistenti e quindi riprendono le richieste dopo che è stata eseguita una Garbage Collection completa.

C#
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;
}

L'esempio di codice completo è il seguente:

C#
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;
        }
    }
}

Vedi anche