Подтвердить что ты не робот

Почему вызов AppDomain.Unload не приводит к сбору мусора?

Когда я выполняю AppDomain.Unload(myDomain), я ожидаю, что он также сделает полную сборку мусора.

По словам Джеффри Рихтера в "CLR через С#", он говорит, что во время AppDomain.Unload:

CLR заставляет собирать мусор, возвращая память, используемую любыми объектами которые были созданы теперь выгруженным AppDomain. Методы Finalize для эти объекты называются, давая объектам возможность правильно очистить себя.

В соответствии с "Стивеном Пратшнером" в разделе "Настройка среды выполнения Common Framework.NET Framework":

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

Я неправильно истолковал их слова? Я сделал следующее решение для воспроизведения неожиданного поведения (в .net 2.0 sp2):

Проект библиотеки классов под названием "Интерфейсы" , содержащий этот интерфейс:

   public interface IXmlClass
    {
        void AllocateMemory(int size);

        void Collect();
    }

Проект библиотеки классов, называемый "ClassLibrary1", который ссылается на "Интерфейсы" и содержит этот класс:

public class XmlClass : MarshalByRefObject, IXmlClass
{

    private byte[] b;

    public void AllocateMemory(int size)
    {
        this.b = new byte[size];
    }

    public void Collect()
    {
        Console.WriteLine("Call explicit GC.Collect() in " + AppDomain.CurrentDomain.FriendlyName + " Collect() method");
        GC.Collect();
        Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    }

    ~XmlClass()
    {
        Console.WriteLine("Finalizing in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName);
    }
}

Проект консольного приложения, который ссылается на проект "Интерфейсы" и выполняет следующую логику:

static void Main(string[] args)
{
    AssemblyName an = AssemblyName.GetAssemblyName("ClassLibrary1.dll");
    AppDomain appDomain2 = AppDomain.CreateDomain("MyDomain", null, AppDomain.CurrentDomain.SetupInformation);
    IXmlClass c1 = (IXmlClass)appDomain2.CreateInstanceAndUnwrap(an.FullName, "ClassLibrary1.XmlClass");
    Console.WriteLine("Loaded Domain {0}", appDomain2.FriendlyName);
    int tenmb = 1024 * 10000;
    c1.AllocateMemory(tenmb);
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    c1.Collect();
    Console.WriteLine("Unloaded Domain{0}", appDomain2.FriendlyName);
    AppDomain.Unload(appDomain2);
    Console.WriteLine("Number of collections after unloading appdomain:  Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    Console.WriteLine("Perform explicit GC.Collect() in Default Domain");
    GC.Collect();
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    Console.ReadKey();
}

Вывод при запуске консольного приложения:

Loaded Domain MyDomain
Number of collections: Gen0:0 Gen1:0 Gen2:0
Call explicit GC.Collect() in MyDomain Collect() method
Number of collections: Gen0:1 Gen1:1 Gen2:1
Unloaded Domain MyDomain
Finalizing in AppDomain MyDomain
Number of collections after unloading appdomain:  Gen0:1 Gen1:1 Gen2:1
Perform explicit GC.Collect() in Default Domain
Number of collections: Gen0:2 Gen1:2 Gen2:2

Замечания:

  • Сбор мусора выполняется для каждого процесса (просто обновление)

  • Объекты в appdomain, которые выгружаются, имеют финализатор, но сбор мусора не выполняется. Объект размером 10 мегабайт, созданный AllocateMemory(), будет собран только после выполнения явного GC.Collect() в приведенном выше примере (или сборщик мусора будет через некоторое время позже.

Другие примечания: на самом деле не имеет значения, завершен ли XmlClass или нет. Такое же поведение наблюдается в приведенном выше примере.

Вопросы:

  • Почему вызов AppDomain.Unload не приводит к сбору мусора? Есть ли способ сделать этот вызов результатом сбора мусора?

  • Внутри AllocateMemory() Я планирую загружать недолговечные большие XML-документы (меньше или равные 16 мб), которые попадут на кучу LargeObject и станут объектами поколения 2. Есть ли способ собрать память, не прибегая к явному GC.Collect() или другому явному программному управлению сборщиком мусора?

4b9b3361

Ответ 1

Дополнительные примечания:

После некоторого обмена сообщениями с Джеффри Рихтером, который был достаточно любезен, чтобы посмотреть на вопрос:

Хорошо, я прочитал ваше сообщение.
Во-первых, массив не будет GCd до тех пор, пока объект XMLClass не будет GCd, и для получения этого объекта требуется два TWO GC, поскольку он содержит метод Finalize.
Во-вторых, разгрузка appdomain, по крайней мере, выполняет этап маркировки GC, поскольку это единственный способ определить, какие объекты недоступны, чтобы можно было вызвать их методы Finalize.
Однако компактная часть GC может быть или не быть выполнена при разгрузке GC. Вызов GC.CollectionCount очевидный, не рассказывает всю историю. Он не показывает, что фаза маркировки GC действительно произошла.
И, возможно, что AppDomain.Unload запускает GC через некоторый внутренний код, который не приводит к увеличению индексов count коллекции. Мы уже знаем, что фаза маркировки выполняется и что подсчет коллекции не отражает этого.

Лучшим тестом было бы рассмотреть некоторые адреса объектов в отладчике и посмотреть, действительно ли происходит уплотнение. Если это так (и я подозреваю, что это так), то подсчет коллекции просто не обновляется правильно.

Если вы хотите опубликовать это на веб-сайте в качестве своего ответа, вы можете.

После принятия его совета и поиска в SOS (также удалил финализатор) он показал это:

До AppDomain.Unload:

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  02f75470 0x007a4470(8012912)
Total Size  0x7e5464(8279140)
------------------------------
GC Heap Size  0x7e5464(8279140)

После AppDomain.Unload(такие же адреса не было выполнено сжатие кучи)

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  02f75470 0x007a4470(8012912)
Total Size  0x7e5464(8279140)
------------------------------
GC Heap Size  0x7e5464(8279140)

После GC.Collect() адреса различаются, указывая, что было выполнено уплотнение кучи.

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x01811234
generation 1 starts at 0x0180b1f0
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  027d3240 0x00002240(8768)
Total Size   0x43234(274996)
------------------------------
GC Heap Size   0x43234(274996)

После того, как я пришел к выводу, что это, безусловно, по дизайну, и что уплотнение кучи не обязательно выполняется. Единственное, что вы действительно можете быть уверены во время разгрузки AppDomain, - это то, что объекты будут помечены как недоступные и будут собраны во время следующей сборки мусора (что, как я уже сказал, не выполняется точно при разгрузке вашего домена приложения, если только совпадение).

EDIT: Я также спросил Маони Стивенса, который работает непосредственно в команде GC. Вы можете прочитать ее ответ где-нибудь в комментариях здесь. Она подтверждает, что это по дизайну. Дело закрыто:)

Ответ 2

  • Возможно, по дизайну, но я не понимаю, зачем вам это поведение (явный GC.Collect). Пока вызываются финализаторы, объекты удаляются из очереди финализатора и готовы к сбору мусора, если это необходимо (поток gc будет срабатывать, когда это необходимо).

  • Вероятно, вы можете использовать какое-то неприятное неуправляемое выделение и какой-то тяжелый интерфейс или код в неуправляемом С++, а затем использовать управляемую оболочку для доступа к ней через С#, но пока вы остаетесь в управляемом мире .Net, нет.

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