Поведение GC несовместимо между 32-битными и 64-битными приложениями
Я заметил несогласованное поведение GC при компиляции консольных приложений как в 32-битной, так и в 64-битной среде в.Net 4.0 с использованием VS 2013.
Рассмотрим следующий код:
class Test
{
public static bool finalized = false;
~Test()
{
finalized = true;
}
}
И в Main()
...
var t = new Test();
t = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (!Test.finalized)
throw new Exception("oops!");
При работе в 64-битном (отладочном) режиме это работает каждый раз без сбоев; однако, работая в 32-битном режиме, я не могу заставить этот объект быть собранным (даже если я создаю больше объектов и жду некоторого периода времени, который я пробовал).
У кого-нибудь есть идеи, почему это так? Это вызывает у меня проблемы при попытке отладки некоторого кода, который должен иметь дело с выпуском неуправляемых прокси-данных для 32-разрядной версии сборок. В 32-битном режиме есть много объектов, которые просто остаются там долгое время спустя (нет, в 64-битном).
Я пытаюсь что-то отладить в 32-битном режиме, но финализаторы не вызываются (по крайней мере, не принудительно). Объекты просто сидят и никогда не собираются (я вижу все слабые ссылки, все еще имеющие значение). В 64-битном режиме все слабые ссылки очищаются, как и ожидалось, и все финализаторы вызываются.
Примечание. Хотя приведенный выше код имеет очень малый масштаб, я заметил, что в 32-разрядном режиме гораздо больше объектов зависло в ГХ, пока в дальнейшем не будет создано больше объектов (даже при вызове "Collect" и "WaitForPendingFinalizers"). Это никогда не происходит в 64-битном режиме. У меня есть один пользователь, который интересуется, почему так много объектов не собирается, что побудило меня к расследованию, и я обнаружил, что в 64-битном режиме все работает лучше, чем в 32. Просто пытаюсь понять, почему.
Изменить: Вот лучший код, чтобы показать различия:
class Program
{
class Test
{
public static bool Finalized = false;
public int ID;
public Test(int id)
{
ID = id;
}
~Test()
{ // <= Put breakpoint here
Finalized = true;
Console.WriteLine("Test " + ID + " finalized.");
}
}
static List<WeakReference> WeakReferences = new List<WeakReference>();
public static bool IsNet45OrNewer()
{
// Class "ReflectionContext" exists from .NET 4.5 onwards.
return Type.GetType("System.Reflection.ReflectionContext", false) != null;
}
static void Main(string[] args)
{
Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
Console.WriteLine("IntPtr: " + IntPtr.Size + Environment.NewLine);
Console.WriteLine("Creating the objects ...");
for (var i = 0; i < 10; ++i)
WeakReferences.Add(new WeakReference(new Test(i)));
Console.WriteLine("Triggering collect ...");
GC.Collect();
Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
GC.WaitForPendingFinalizers();
Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");
bool ok = true;
for (var i = 0; i < 10; ++i)
if (WeakReferences[i].IsAlive)
{
var test = (Test)WeakReferences[i].Target;
if (test != null)
Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
ok = false;
}
if (ok)
Console.WriteLine("All Test objects successfully collected and finalized.");
Console.WriteLine(Environment.NewLine + "Creating more objects ...");
for (var i = 0; i < 10; ++i)
WeakReferences.Add(new WeakReference(new Test(i)));
Console.WriteLine("Triggering collect ...");
GC.Collect();
Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
GC.WaitForPendingFinalizers();
Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");
ok = true;
for (var i = 0; i < 10; ++i)
if (WeakReferences[i].IsAlive)
{
var test = (Test)WeakReferences[i].Target;
if (test != null)
Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
ok = false;
}
if (ok)
Console.WriteLine("All Test objects successfully collected and finalized.");
Console.WriteLine(Environment.NewLine + "Done.");
Console.ReadKey();
}
}
Он работает в 64-битной, но не в 32-битной. В моей системе "Тест №9" никогда не собирается (остается слабая ссылка), даже после создания большего количества объектов и повторной попытки.
К вашему сведению: главная причина задавать вопрос, потому что у меня есть \gctest
вариант в моей консоли, чтобы проверить сборку мусора между V8.Net и базовым движком V8 на родной стороне. Работает в 64-битной, но не 32-битной версии.
1 ответ
Нет репродукций на VS2013 + .NET 4.5 на x86 и x64:
class Program
{
class Test
{
public readonly int I;
public Test(int i)
{
I = i;
}
~Test()
{
Console.WriteLine("Finalizer for " + I);
}
}
static void Tester()
{
var t = new Test(1);
}
public static bool IsNet45OrNewer()
{
// Class "ReflectionContext" exists from .NET 4.5 onwards.
return Type.GetType("System.Reflection.ReflectionContext", false) != null;
}
static void Main(string[] args)
{
Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
Console.WriteLine("IntPtr: " + IntPtr.Size);
var t = new Test(2);
t = null;
new Test(3);
Tester();
Console.WriteLine("Pre GC");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Post GC");
Console.ReadKey();
}
}
Обратите внимание, что (как заметил @Sriram Sakthivel), могут существовать "призрачные" копии вашей переменной, срок жизни которых продлен до конца метода (когда вы компилируете код в режиме отладки или у вас подключен отладчик (F5 вместо Ctrl+F5), срок жизни ваших переменных продлен до конца метода, чтобы облегчить отладку)
Например, инициализация объекта (когда вы делаете что-то вроде new Foo { I = 5 }) создает скрытую локальную переменную.