Во-первых, извинения за длину этого вопроса, но я хочу полностью объяснить себя с самого начала.Уведомления о сборе мусора
ОК, немного фона. Я работал над некоторым кодом, который реализует шаблон 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 не поднимают события.
Теперь (наконец) возникает вопрос.
Я озадачен этим и должен понять, почему это происходит. Мое рассуждение состоит в том, что, поскольку нет никакой гарантии, когда поток финализатора будет запущен, возможно, что между экземплярами выполняемого потока финализатора может возникнуть множество коллекций мусора (любого поколения).
Если это так, то единственной гарантией, которая может быть предоставлена, является то, что если событие поднято, сбор мусора этого поколения произошел с момента последнего появления события.
Это лучшее, что я могу придумать, но я был бы очень признателен, если бы кто-то, у кого больше знания внутренних органов по сборке мусора, мог подтвердить, правильно ли он, и у меня нет большой ошибки реализации в моем решении.
Спасибо за подшипник со мной.
Я не хочу набирать это как ответ, потому что я только касаюсь вашего предположения. Когда GC запускается и находит объект для сбора, если он имеет финализатор, он помещается в свободный список, который удаляет его из GC, что делает его неприемлемым для коллекции *. Поэтому, когда финализатор работает, GC фактически не полностью выполнен на этом объекте, он только заметил его, отложил его для дальнейшей обработки. Только после выполнения финализатора и на следующем GC, объект будет фактически собран. Не уверен, что это что-то делает для вашего вопроса. –
Кроме того, я не уверен, что код Richter, который вы опубликовали, не может быть изменен, чтобы сообщать коллекции Gen1, но это потребует некоторой обманчивости и, вероятно, не поймает их всех. –
И да, финализатор нити тянется вдоль финиширующих объектов в свободном списке, в своем собственном темпе. Он работает не только между коллекциями, поэтому вы не поймаете все коллекции, используя только эту технику. Могу я спросить * почему * вам нужно поймать коллекции мусора? Для описанной проблемы вам действительно нужно реагировать на * каждый цикл сбора мусора? –