Достижимость объекта GC.collect()

#d

#d

Вопрос:

Объекты, на которые больше нет ссылок, не могут быть немедленно собраны мусором с помощью GC.collect(), однако промежуточный вызов, например, new, writeln или Thread.sleep сделает объект без ссылок доступным с помощью GC.collect().

 import std.stdio;
import core.thread;
import core.memory;

class C
{
    string m_str;
    this(string s) {this.m_str = s;}
    ~this() { writeln("Destructor: ",this.m_str); }
}

void main()
{
    {
        C c1 = new C("c1");
    }   
    {
        C c2 = new C("c2");
    }
    //writeln("Adding this writeln means c2 gets destructed at first GC.collect.");
    //Thread.sleep( 1 ); // Similarly this call means c2 gets destructed at first GC.collect.
    //int x=0; for (int i=0; i<1000_000_000;  i) x =2*i; // Takes time, but does not make c2 get destructed at first GC.collect.
    GC.collect();
    writeln("Running second round GC.collect");
    GC.collect();
    writeln("Exiting...");
}
  

Приведенный выше код возвращает:

Деструктор: c1
запускает второй раунд GC.сбор
Деструктора: c2
Завершается…

Кто-нибудь может объяснить эту достижимость объектов во время сборки мусора?

Комментарии:

1. Забыл упомянуть, что вышесказанное доступно в 32-разрядной версии Windows, dmd.2.052, без флагов компилятора.

2. Пожалуйста, обратите внимание, что, поскольку D GC является консервативным, это не гарантирует, что какой-либо объект, на который нет ссылок, когда-либо будет собран.

Ответ №1:

Я не знаком с деталями сборки мусора D, но общий метод заключается в том, чтобы начинать со всех «корневых указателей», чтобы выяснить, какие объекты являются активными. Когда все компилируется вплоть до машинного кода, это означает запуск из стека вызовов функций и регистров процессора.

Приведенный выше код может быть скомпилирован до чего-то вроде:

 $r0 = new C("c1")
$r0 = new C("c2")
GC.collect()
writeln("Running second round GC.collect")
GC.collect()
writeln("Exiting...")
  

При первом GC.collect() больше нет ссылок на первый объект (поскольку $r0 он был перезаписан). И хотя второй объект не используется, в $r0 все еще есть указатель на него, поэтому GC консервативно предполагает, что он достижим. Обратите внимание, что компилятор, возможно, мог бы выполнить очистку $r0 после того, как переменная c2 выйдет за пределы области видимости, но это замедлило бы выполнение кода.

Когда выполняется первый writeln вызов, он, вероятно, использует register $r0 внутри и поэтому очищает ссылку на второй объект. Вот почему второй объект восстанавливается после второго вызова GC.collect() .

Комментарии:

1. Большое спасибо за ваш ответ, это было обязательным условием перед моим следующим вопросом о том, какой код может безопасно находиться в деструкторе.