Продолжительность жизни объектов в Java против .Net - PullRequest
11 голосов
/ 11 января 2012

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

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

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

Итак, мой вопрос: как насчет Java?Я написал эту программу для проверки такого поведения, и похоже, что объект остается в живых.Я не думаю, что JVM может ограничить время жизни переменной при интерпретации байт-кода, поэтому я попытался запустить программу с помощью 'java -Xcomp' для принудительной компиляции метода, но 'finalize' в любом случае не вызывается.Похоже, что это не так для 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 via C #", примерно так:

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 ()) и вызывается каждую секунду, если target является «Debug» (срок жизни переменных увеличился, поскольку программист может попытаться получить доступ к объекту с помощью отладчика).Но на Mono callback вызывается каждую секунду независимо от того, как вы его компилируете.Похоже, реализация Mono 'Timer' хранит ссылку на экземпляр где-то в пуле потоков.Реализация MS не делает этого.

Ответы [ 2 ]

4 голосов
/ 11 января 2012

Обратите внимание, что если объект можно собрать, это не означает, что он будет фактически собран в любой заданной точке - поэтому ваш метод может выдавать ложные отрицания.Если вызывается метод finalize какого-либо объекта, вы определенно можете сказать, что он недоступен, но если метод не вызывается, вы не сможете логически вывести что-либо.Как и в большинстве вопросов, связанных с GC, недетерминизм сборщика мусора затрудняет разработку тестов / гарантий о том, что именно он будет делать.

По теме достижимости / собираемости, JLS говорит(12.6.1):

A достижимый объект - это любой объект, к которому можно получить доступ в любом потенциальном продолжающемся вычислении из любого живого потока.Могут быть разработаны оптимизирующие преобразования программы, которые уменьшают число достижимых объектов до меньшего, чем те, которые наивно считаются достижимыми.Например, компилятор или генератор кода может установить переменную или параметр, которые больше не будут использоваться для обнуления, чтобы в будущем хранилище для такого объекта могло быть потенциально восстановимо.

Что болееили не совсем то, что вы ожидаете - я думаю, что приведенный выше абзац изоморфен: «объект недоступен, если вы определенно не будете его больше использовать».

Возвращаясь к исходной ситуации, Можете ли вы подумать о каких-либо практических последствиях между объектом, который считается недостижимым после строки 1, а не строкой 2 ?Моя первоначальная реакция заключается в том, что их нет, и если вам как-то удастся найти такую ​​ситуацию, это, вероятно, будет признаком плохого / искаженного кода, вызывающего виртуальную машину, а не врожденной слабостью языка.

Хотя я открыт для контраргументов.


Редактировать: Спасибо за интересный пример.

Я согласен с вашей оценкой и вижу, куда вы идете, хотя проблема в том,Возможно, более того, что режим отладки слегка меняет семантику вашего кода.

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

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

1 голос
/ 11 января 2012

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...