Подтвердить что ты не робот

Продолжительность жизни объектов в Java vs..Net

Я читал "CLR через С#", и кажется, что в этом примере объект, который был первоначально назначен "obj", будет иметь право на сборку мусора после выполнения строки 1, а не после строки 2.

void Foo()
{
    Object obj = new Object();
    obj = null;
}

Это потому, что срок жизни локальной переменной определен не по объему, в котором он был определен, но в последний раз, когда вы его прочитали.

Итак, мой вопрос: как насчет Java? Я написал эту программу, чтобы проверить такое поведение, и похоже, что объект остается в живых. Я не думаю, что JVM может ограничить время жизни переменной при интерпретации байт-кода, поэтому я попытался запустить программу с помощью java -Xcomp для принудительной компиляции методов, но "финализировать" все равно не вызывается. Похоже, что это не так для Java, но я надеюсь, что получу более точный ответ. Также, как насчет Android Dalvik VM?

class TestProgram {

    public static void main(String[] args) {
        TestProgram ref = new TestProgram();
        System.gc();
    }

    @Override
    protected void finalize() {
        System.out.println("finalized");
    }
}

Добавлено: Джеффри Рихтер дает пример кода в "CLR через С#", примерно так:

public static void Main (string[] args)
{
    var timer = new Timer(TimerCallback, null, 0, 1000); // call every second
    Console.ReadLine();
}

public static void TimerCallback(Object o)
{
    Console.WriteLine("Callback!");
    GC.Collect();
}

TimerCallback вызывается только один раз на MS.Net, если целью проекта является "Release" (таймер уничтожается после вызова GC.Collect()) и вызывается каждую секунду, если целью является "Debug" (продолжительность жизни переменных увеличивается, поскольку программист может попытаться получить доступ объект с отладчиком). Но на Mono callback вызывали каждую секунду независимо от того, как вы его компилируете. Похоже, реализация Mono 'Timer' хранит ссылку на экземпляр где-то в пуле потоков. Реализация MS не делает этого.

4b9b3361

Ответ 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, это нормально, так как нет никаких гарантий относительно того, что вы увидите, в зависимости от того, как вы его вызываете. (Это концептуально похоже на то, как таймер, выполняющийся в рамках задачи с интенсивным использованием ЦП, будет больше гадать, когда система находится под нагрузкой, - ни результат не ошибочен, потому что интерфейс не обеспечивает такую ​​гарантию.)

В этот момент я отсылаю вас к первому предложению в этом ответе.:)

Ответ 2

Java, в общем, имеет поведение, которое, если объект доступен в области видимости (есть ссылка на него, которая не является мусором), тогда объект не является мусором. Это рекурсивно, поэтому, если a является ссылкой на объект, который имеет ссылку на b, объект b означает не мусор.

В пределах области, где вы все еще можете достичь объекта, на который ссылается ref (вы можете добавить строку System.out.println(ref.toString())), ref не мусор.

Однако, согласно этот старый источник с сайта Sun, большая часть зависит от конкретной реализации JVM.