Почему вызов AppDomain.Unload не приводит к сборке мусора? - PullRequest
19 голосов
/ 27 апреля 2010

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

По словам Джеффри Рихтера в «CLR via C #», он говорит, что во время AppDomain.Unload:

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

Согласно «Стивену Пратшнеру» в «Настройка общеязыковой среды выполнения .NET Framework»:

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

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

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

   public interface IXmlClass
    {
        void AllocateMemory(int size);

        void Collect();
    }

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

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

На что обратить внимание:

  1. Сборка мусора выполняется за процесс (только переподготовка)

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

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

Вопросы:

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

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

Ответы [ 2 ]

17 голосов
/ 29 апреля 2010

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

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

ОК, я прочитал ваш пост.
Во-первых, массив не будет GC’d до тех пор, пока объект XMLClass не станет GC’d, и для сбора этого объекта потребуется ДВА GC, поскольку он содержит метод Finalize.
Во-вторых, выгрузка домена приложения, по крайней мере, выполняет этап разметки GC, поскольку это единственный способ определить, какие объекты недоступны, чтобы можно было вызывать их методы Finalize.
Тем не менее, компактная часть GC может или не может быть сделано при выгрузке GC. Очевидный вызов GC.CollectionCount не говорит всей истории. Это не показывает, что фаза маркировки GC действительно произошла.
И, возможно, AppDomain.Unload запускает сборщик мусора через некоторый внутренний код, который не приводит к увеличению переменных счетчика коллекции. Мы уже знаем наверняка, что этап маркировки выполняется, и что подсчет сбора не отражает это.

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

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

После его совета и изучения 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)

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

РЕДАКТИРОВАТЬ: Я также спросил Maoni Stephens, который работает непосредственно в команде GC. Вы можете прочитать ее ответ где-нибудь в комментариях здесь . Она подтверждает, что это по замыслу. Дело закрыто:)

5 голосов
/ 27 апреля 2010
  1. Вероятно, по замыслу, но я не понимаю, почему вы хотите такое поведение (явный GC.Collect). Пока вызываются финализаторы, объекты удаляются из очереди финализатора и готовы к сборке мусора, если требуется (поток gc запускается при необходимости).

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

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

...