Хорошо известно, что сборщик мусора .NET не просто "удаляет" объекты в куче, но также борется с фрагментацией памяти, используя уплотнение памяти. Насколько я понимаю, в основном память копируется на новое место, а старое место в какой-то момент удаляется.
Мой вопрос: как это работает?
Мне больше всего любопытно, что GC работает в отдельном потоке, что означает, что объект, над которым мы работаем, может быть перемещен GC, пока мы выполняем наш код.
Технические детали вопроса
Чтобы проиллюстрировать, позвольте мне более подробно объяснить мой вопрос:
class Program
{
private int foo;
public static void Main(string[] args)
{
var tmp = new Program(); // make an object
if (args.Length == 2) // depend the outcome on a runtime check
{
tmp.foo = 12; // set value ***
}
Console.WriteLine(tmp.foo);
}
}
В этом маленьком примере мы создаем объект и устанавливаем простую переменную для объекта. Точка "***" имеет значение для вопроса: если адрес "tmp" перемещается, "foo" будет ссылаться на что-то неправильное, и все сломается.
Сборщик мусора запускается в отдельном потоке. Насколько я знаю, "tmp" можно перемещать во время этой инструкции, а "foo" может привести к неправильному значению. Но как-то происходит волшебство, и это не так.
Что касается дизассемблера, я заметил, что скомпилированная программа действительно берет адрес "foo" и перемещается в значение "12:
000000ae 48 8B 85 10 01 00 00 mov rax,qword ptr [rbp+00000110h]
000000b5 C7 40 08 0C 00 00 00 mov dword ptr [rax+8],0Ch
Я более или менее ожидал увидеть косвенный указатель здесь, который может быть обновлен, но, по-видимому, GC работает умнее, чем это.
Кроме того, я не вижу ни одной синхронизации потоков, которая проверяет, был ли объект перемещен. Итак, как GC обновляет состояние в исполняемом потоке?
Итак, как это работает? И если GC не перемещает эти объекты, что такое "правило", которое определяет, не перемещать или не перемещать объекты?