Как среда выполнения .NET перемещает память?

Хорошо известно, что сборщик мусора .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 не перемещает эти объекты, что такое "правило", которое определяет, не перемещать или не перемещать объекты?

Ответ 1

.NET GC (по крайней мере частично) GC "stop-the-world": он останавливает управляемые потоки, прежде чем выполнять свою работу, выполняет свою работу, а затем перезапускает управляемые потоки.

GC рабочей станции может быть параллельной (так частично не остановить-мир), но обратите внимание на https://msdn.microsoft.com/library/ee851764.aspx.

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

Обратите внимание, что со всеми GC, gen0 и gen1 всегда являются stop-the-world. Таким образом, они могут без проблем перемещать блоки памяти. Только gen2 можно сделать в фоновом режиме с помощью некоторых GC с некоторыми конфигурациями (эта ссылка, информация немного фрагментирована по всей странице), поэтому всегда является "миром-остановленным" моментом, когда память, которая была освобождена, может быть уплотнена.