Ответ 1
Обратите внимание, что только потому, что объект может быть собран, это не означает, что он действительно будет собран в любой заданной точке - поэтому ваш метод может давать ложные негативы. Если вы вызываете какой-либо объект finalize
, вы можете определенно сказать, что он недоступен, но если метод не вызывается, вы не можете логически выводить что-либо. Как и в большинстве вопросов, связанных с GC, недетерминизм сборщика мусора затрудняет разработку тестов/гарантий относительно того, что он будет делать.
По вопросу о достижимости/собираемости, JLS говорит (12.6.1):
Досягаемым объектом является любой объект, к которому можно получить доступ в любом потенциальном продолжающемся вычислении из любой живой нити. Можно оптимизировать преобразования программы, которые уменьшают количество объектов, которые достижимы, меньше, чем те, которые наивно считаются доступными. Например, генератор компилятора или кода может выбрать установку переменной или параметра, который больше не будет использоваться для нулевого значения, чтобы скорее восстановить потенциальную возможность хранения такого объекта раньше.
Это более или менее точно то, что вы ожидаете - я думаю, что вышеприведенный параграф изоморфен "объект недоступен, если вы определенно не будете его использовать"
Возвращаясь к исходной ситуации, , вы можете придумать какие-либо практические последствия между объектом, который считается недостижимым после строки 1, в отличие от строки 2? Моя первоначальная реакция заключается в том, что их нет, и если вам каким-то образом удалось найти такую ситуацию, это, скорее всего, будет признаком плохого/скрученного кода, заставляющего виртуальную машину бороться, а не присущую ему слабость в языке.
Хотя я открыт для встречных аргументов.
Изменить: Спасибо за интересный пример.
Я согласен с вашей оценкой и вижу, куда вы идете, хотя проблема, вероятно, в том, что режим отладки тонко изменяет семантику вашего кода.
В написанном коде вы присваиваете Timer
локальной переменной, которая впоследствии не читается в пределах ее области. Даже самый тривиальный анализ эвакуации может показать, что переменные Timer
не используются нигде в методе main
и поэтому могут быть устранены. Поэтому я думаю, что ваша первая строка может считаться в точности эквивалентной просто вызову конструктора напрямую:
public static void Main (string[] args)
{
new Timer(TimerCallback, null, 0, 1000); // call every second
...
В этом последнем случае ясно, что вновь созданный объект Timer
недоступен сразу после построения (предполагая, что он не делает ничего скрытого, как добавить себя в статические поля и т.д. в его конструкторе); и так, чтобы он был собран, как только GC приблизился к нему.
Теперь в случае отладки все выглядит по-разному, по той причине, о которой вы упоминали, что разработчик может захотеть проверить состояние локальных переменных позже в методе. Поэтому компилятор (и JIT-компилятор) не может оптимизировать их; он как будто есть доступ к переменной в конце метода, предотвращая сбор до этой точки.
Тем не менее, я не думаю, что это фактически изменяет семантику. Характер GC заключается в том, что сбор редко гарантируется (по крайней мере, на Java, единственная гарантия, которую вы получаете, заключается в том, что если OutOfMemoryError был выброшен, то все, что считалось недостижимым, было GCed сразу же). Фактически, предполагая, что у вас достаточно места для кучи, чтобы удерживать каждый объект, созданный в течение всего жизненного цикла среды выполнения, реализация GC-протокола no-op совершенно допустима. Поэтому, хотя вы можете наблюдать поведенческие изменения в том, сколько раз тики Timer
, это нормально, так как нет никаких гарантий относительно того, что вы увидите, в зависимости от того, как вы его вызываете. (Это концептуально похоже на то, как таймер, выполняющийся в рамках задачи с интенсивным использованием ЦП, будет больше гадать, когда система находится под нагрузкой, - ни результат не ошибочен, потому что интерфейс не обеспечивает такую гарантию.)
В этот момент я отсылаю вас к первому предложению в этом ответе.:)