Когда я выполняю 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() или другому явному программному управлению сборщиком мусора?