2013-07-17 5 views
1

Во-первых, извинения за длину этого вопроса, но я хочу полностью объяснить себя с самого начала.Уведомления о сборе мусора

ОК, немного фона. Я работал над некоторым кодом, который реализует шаблон Weak Event, используя объект WeakReference. При этом я столкнулся с обычной проблемой утечки объектов в определенных сценариях, когда издатель перестает поднимать события. Если вас это интересует, там будет справедливая битка information. Команда WPF внедрила WeakEventManager, чтобы решить проблему, которая, по моему мнению, использует планировщик для проверки любых просочившихся объектов и избавления от них.

Я решил попробовать другой подход. Вместо использования планировщика я хотел вызвать обнаружение и отмену пропущенных объектов с помощью сборщиков мусора. Это кажется логичным для меня, поскольку цели любых объектов WeakReference удаляются только в результате коллекций. Это привело меня ко всему, что привело бы к событию, когда произошла сборка мусора. Во-первых, я изучил использование механизма рамки GC.RegisterForFullGCNotification, но быстро понял, что это невозможно, поскольку оно cannot be used with concurrent garbage collections. Затем я сделал небольшую часть чтения по этой теме и нашел Jeffrey Richter's solution, но у этого есть несколько проблем и только оповещения вы коллекций gen0 и gen2.

Короче говоря, я построю следующий простой класс. Цель этого класса - генерировать уведомления о событиях, которые произошла в сборке мусора. Однако механизм генерации этих уведомлений основан на завершении объекта детектора. Таким образом, события НЕ поднимаются, когда происходит сбор мусора, но потом, когда выполняется поток финализатора.

using System; 

namespace Test 
{ 
    /// <summary> 
    /// Class responsible for monitoring garbage collections. 
    /// </summary> 
    public static class GarbageCollectionMonitor 
    { 
     private static readonly object syncLock; 
     private static int generation0CollectionCount; 
     private static EventHandler<EventArgs> generation0Subscriptions; 
     private static int generation1CollectionCount; 
     private static EventHandler<EventArgs> generation1Subscriptions; 
     private static int generation2CollectionCount; 
     private static EventHandler<EventArgs> generation2Subscriptions; 

     public static event EventHandler<EventArgs> Generation0GarbageCollected 
     { 
      add 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation0Subscriptions = (EventHandler<EventArgs>)Delegate.Combine( GarbageCollectionMonitor.generation0Subscriptions, 
                                value); 
       } 
      } 

      remove 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation0Subscriptions = (EventHandler<EventArgs>)Delegate.Remove( GarbageCollectionMonitor.generation0Subscriptions, 
                                value); 
       } 
      } 
     } 

     public static event EventHandler<EventArgs> Generation1GarbageCollected 
     { 
      add 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation1Subscriptions = (EventHandler<EventArgs>)Delegate.Combine( GarbageCollectionMonitor.generation1Subscriptions, 
                                value); 
       } 
      } 

      remove 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation1Subscriptions = (EventHandler<EventArgs>)Delegate.Remove( GarbageCollectionMonitor.generation1Subscriptions, 
                                value); 
       } 
      } 
     } 

     public static event EventHandler<EventArgs> Generation2GarbageCollected 
     { 
      add 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation2Subscriptions = (EventHandler<EventArgs>)Delegate.Combine( GarbageCollectionMonitor.generation2Subscriptions, 
                                value); 
       } 
      } 

      remove 
      { 
       lock (GarbageCollectionMonitor.syncLock) 
       { 
        GarbageCollectionMonitor.generation2Subscriptions = (EventHandler<EventArgs>)Delegate.Remove( GarbageCollectionMonitor.generation2Subscriptions, 
                                value); 
       } 
      } 
     } 

     /// <summary> 
     /// Constructs the garbage collection monitor type 
     /// </summary> 
     static GarbageCollectionMonitor() 
     { 
      GarbageCollectionMonitor.syncLock = new object(); 

      // Construct a detector object 
      // 
      // N.B. No reference to the detector is held so that it can immediately be 
      // collected by the garbage collector. 
      new Detector(); 
     } 

     /// <summary> 
     /// Class responsible for detecting the operation of the garbage collector 
     /// via its finalization method 
     /// </summary> 
     private sealed class Detector 
     { 
      /// <summary> 
      /// Constructs a detector object 
      /// </summary> 
      public Detector() 
      { 
      } 

      /// <summary> 
      /// Finalizes a detector object 
      /// </summary> 
      ~Detector() 
      { 
       // Get the generation 0 collection count 
       // 
       // Since the finalizer thread is frozen when the garbage collector is 
       // operating there is no danger of race conditions when retrieving the 
       // garbage collection counts 
       int generation0CollectionCount = GC.CollectionCount(0); 

       // Determine if the current generation 0 collection count is greater than 
       // the monitor's generation 0 collection count 
       // 
       // This indicates that a generation 0 garbage collection has taken place 
       // since the last time a detector object was finalized. 
       if (generation0CollectionCount > GarbageCollectionMonitor.generation0CollectionCount) 
       { 
        // Update the monitor's generation 0 collection count to the current 
        // collection count 
        GarbageCollectionMonitor.generation0CollectionCount = generation0CollectionCount; 

        // Process any generation 0 event subscriptions 
        this.ProcessSubscriptions(GarbageCollectionMonitor.generation0Subscriptions); 
       } 

       int generation1CollectionCount = GC.CollectionCount(1); 

       if (generation1CollectionCount > GarbageCollectionMonitor.generation1CollectionCount) 
       { 
        GarbageCollectionMonitor.generation1CollectionCount = generation1CollectionCount; 

        this.ProcessSubscriptions(GarbageCollectionMonitor.generation1Subscriptions); 
       } 

       int generation2CollectionCount = GC.CollectionCount(2); 

       if (generation2CollectionCount > GarbageCollectionMonitor.generation2CollectionCount) 
       { 
        GarbageCollectionMonitor.generation2CollectionCount = generation2CollectionCount; 

        this.ProcessSubscriptions(GarbageCollectionMonitor.generation2Subscriptions); 
       } 

       // Construct a new generation 0 detector object 
       new Detector(); 
      } 

      /// <summary> 
      /// Processes event subscriptions 
      /// </summary> 
      /// <param name="subscriptions">The subscriptions</param> 
      private void ProcessSubscriptions(EventHandler<EventArgs> subscriptions) 
      { 
       // N.B. A local reference to the subscriptions delegate is required because 
       // this method is run on the finalizer thread which is started AFTER the 
       // garbage collector has finished running. As a result it is likely that 
       // the application threads that were frozen by the garbage collector will 
       // have been thawed. Since delegates are immutable, by getting a local 
       // reference the processing of the subscriptions is made thread-safe as any 
       // attempt by another thread to asynchronously add or remove a subscription 
       // will result in a separate new delegate being constructed. 

       // Determine if any event subscriptions need to be invoked 
       // 
       // N.B. If a local reference were not used then there would be a risk of 
       // the following: 
       // 
       // (1) The null reference inequality check yields a true result. 
       // (2) The finalizer thread is paused. 
       // (3) Another thread removes all subscriptions to the event causing the 
       // subscriptions delegate to be replaced with a null reference. 
       // (4) The finalizer thread is unpaused. 
       // (5) The attempt to invoke the subscriptions delegate results in a null 
       // reference exception being thrown. 
       if (subscriptions != null) 
       { 
        // Invoke the event 
        subscriptions.Invoke( this, 
              EventArgs.Empty); 
       } 
      } 
     } 
    } 
} 

Это, кажется, работает хорошо, но при тестировании его с помощью следующего кода ...

private void Gen0GarbageCollected( object sender, 
             System.EventArgs e) 
    { 
     Console.Write("Gen0 " + GC.CollectionCount(0) + Environment.NewLine); 
    } 

    private void Gen1GarbageCollected( object sender, 
             System.EventArgs e) 
    { 
     Console.Write("Gen1 " + GC.CollectionCount(1) + Environment.NewLine); 
    } 

    private void Gen2GarbageCollected( object sender, 
             System.EventArgs e) 
    { 
     Console.Write("Gen2 " + GC.CollectionCount(2) + Environment.NewLine); 
    } 

... Я получаю следующие результаты

Gen0 1 
    Gen0 2 
    Gen1 1 
    Gen0 3 
    Gen0 4 
    Gen1 2 
    Gen2 1 
    Gen0 5 
    Gen1 3 
    Gen2 2 
    Gen0 7 
    Gen0 8 
    Gen0 9 
    Gen1 4 
    Gen0 10 
    Gen0 11 
    Gen0 12 
    Gen1 5 
    Gen2 3 
    Gen0 14 
    Gen0 15 
    Gen0 16 
    Gen1 6 
    Gen0 17 

Кажется, что не все сборка мусора вызывает финишную нить. В этом примере 6-я и 13-я коллекции генерации 0 не поднимают события.

Теперь (наконец) возникает вопрос.

Я озадачен этим и должен понять, почему это происходит. Мое рассуждение состоит в том, что, поскольку нет никакой гарантии, когда поток финализатора будет запущен, возможно, что между экземплярами выполняемого потока финализатора может возникнуть множество коллекций мусора (любого поколения).

Если это так, то единственной гарантией, которая может быть предоставлена, является то, что если событие поднято, сбор мусора этого поколения произошел с момента последнего появления события.

Это лучшее, что я могу придумать, но я был бы очень признателен, если бы кто-то, у кого больше знания внутренних органов по сборке мусора, мог подтвердить, правильно ли он, и у меня нет большой ошибки реализации в моем решении.

Спасибо за подшипник со мной.

+0

Я не хочу набирать это как ответ, потому что я только касаюсь вашего предположения. Когда GC запускается и находит объект для сбора, если он имеет финализатор, он помещается в свободный список, который удаляет его из GC, что делает его неприемлемым для коллекции *. Поэтому, когда финализатор работает, GC фактически не полностью выполнен на этом объекте, он только заметил его, отложил его для дальнейшей обработки. Только после выполнения финализатора и на следующем GC, объект будет фактически собран. Не уверен, что это что-то делает для вашего вопроса. –

+0

Кроме того, я не уверен, что код Richter, который вы опубликовали, не может быть изменен, чтобы сообщать коллекции Gen1, но это потребует некоторой обманчивости и, вероятно, не поймает их всех. –

+0

И да, финализатор нити тянется вдоль финиширующих объектов в свободном списке, в своем собственном темпе. Он работает не только между коллекциями, поэтому вы не поймаете все коллекции, используя только эту технику. Могу я спросить * почему * вам нужно поймать коллекции мусора? Для описанной проблемы вам действительно нужно реагировать на * каждый цикл сбора мусора? –

ответ

0

Есть только две ситуаций, когда это действительно важно получить ли мертвые подписки слабых событий очищены:

  • Каждый раз, когда рассматриваемое событие вызывается, система будет тратить время обработки мертвых подписок, приют Убирали.

  • Если неограниченное количество новых подписчиков может быть добавлено без очистки мертвых подписей, количество мертвых подписчиков может расти без ограничений.

Если событие никогда не вызывается и никакие новые подписки не будут добавлены, нет времени будет потрачено обработки мертвых событий, а общий объем памяти, занимаемый событий не будет расти. Следовательно, вообще не будет никакого реального вреда, просто позволяя им сидеть.

Я бы предположил, что вы можете захотеть, чтобы код очистил мертвые события, если они наблюдаются во время вызова события, и в противном случае либо каждый запрос на подписку проверяет пару слабых подписчиков и очищает их, если они умерли , либо запросить подписку на события, проверяя все подписки на события, если число, добавленное с момента последнего сканирования, достаточно превышает число, которое было обнаружено в течение этого сканирования. Возможно, код, который проверяет мертвые подписки, контролирует общее количество циклов GC, которые были выполнены, и пропускает проверки, если количество циклов GC, которые были выполнены до даты, равно числу, которое было выполнено до того, как было запущено последнее сканирование , но это, вероятно, не имеет большого значения.

1

Почему бы не использовать ConditionalWeakTable<TKey, TValue>, где ключ является объектом, который вы хотите контролировать для очистки и значение специально созданный CriticalHandle, который реализует ReleaseHandle устанавливая статический логический флаг в коде, что означает «пришло время, чтобы очистить обработчиков "?

Затем вы можете использовать отдельный таймер для запуска потока/задачи, который проверяет флаг Boolean и, если true, запускает вашу ручную очистку (эта очистка не будет и не должна выполняться во время финализатора или во время вызова до ReleaseHandle).

Смежные вопросы