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

Вопрос с интервью: объекты, пригодные для сбора мусора

Введите следующий код:

class A {
    Boolean b;
    A easyMethod(A a){
        a = null;
        return a;
    }
    public static void main(String [] args){
        A a1 = new A();
        A a2 = new A();
        A a3 = new A();
        a3 = a1.easyMethod(a2);
        a1 = null;
        // Some other code 
    }
}

Вопрос в том, сколько объектов имеет право на сбор мусора прямо перед // Some other code.

Тогда правильный ответ (по крайней мере, ответ интервьюера): 2 - логическое b, потому что это обертка и a1.

Не могли бы вы объяснить мне, почему a2 и a3 не собираются мусор?

LATER EDIT:

  • Хорошо, думаю, я понял это сейчас. Сначала это было немного странно, но теперь я уверен, что интервьюер ошибся. Моя первоначальная ошибка заключалась в том, что сначала я не считал, что Java передается только по значению, поэтому невозможно сделать a2 null внутри функции, которая принимает "a2" в качестве параметра, потому что a2 на самом деле является копией a2.
  • Часть с булевым b действительно была очевидна.

Спасибо за ответ, я пришлю несколько отзывов для интервью после этого:).

4b9b3361

Ответ 1

Предполагая, что go предполагается easyMethod, он работает следующим образом

class A {
    Boolean b;
    A easyMethod(A a){
        a = null; // the reference to a2 was passed in, but is set to null
                  // a2 is not set to null - this copy of a reference is!
        return a; // null is returned
    }
    public static void main(String [] args){
        A a1 = new A(); // 1 obj
        A a2 = new A(); // 2 obj
        A a3 = new A(); // 3 obj
        a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why
        a1 = null; // so far, a1 and a3 have been set to null and flagged
        // Some other code 
    }
}

Два объекта имеют право на сбор мусора (a1 и a3). b не потому, что это только ссылка на null. Не было Boolean.

Чтобы обойти безумные тонкости того, что может быть // Some other code, я вместо этого задаю вопрос, который будет переформулирован следующим образом:

Указать и объяснить следующий вывод:

class A {
    int i;
    A(int i) { this.i = i; }
    public String toString() { return ""+i; }
    A go(A a){
        a = null; // the reference to a2 was passed in, but is set to null
                  // a2 is not set to null - this copy of a reference is!
        return a; // null is returned
    }
    public static void main(String [] args){
        A a1 = new A(1); // 1 obj
        A a2 = new A(2); // 2 obj
        A a3 = new A(3); // 3 obj
        a3 = a1.go(a2); // a3 set to null and flagged for GC - see above for why
        a1 = null; // so far, a1 and a3 have been set to null and flagged

        test(a1);
        test(a2);
        test(a3);

    }
    static void test(A a) {
        try { System.out.println(a); } 
        catch(Exception e) { System.out.println((String)null); }
    }
}

И вывод:

c:\files\j>javac A.java

c:\files\j>java A
null
2
null

И следующее: в этой точке a1 и a3 имеют право на GC, а a2 - нет.

Урок этого вопроса состоит в том, что "Передача объектной ссылки на метод и установка ссылки на нуль не приводят к обнулению исходной ссылки". Это те знания, которые пытался опросить интервьюер.

Ответ 2

Если a1.go(a2) на самом деле означает a1.easyMethod(a2), ответ действительно 2, но не те, которые вы указали. Как правильно заметил Божо, b не инициализируется, поэтому он не относится ни к одному объекту. Два объекта, подходящих для сбора мусора в точке комментария, - это те, на которые первоначально ссылались a1 и a3.

a1 явно отключен, а a3 переназначается на возвращаемое значение a1.easyMethod(a2), которое равно null. Тем не менее, a2 не влияет на вызов метода, так как Java передается по значению, поэтому в метод передается только копия ссылки a2. Хотя копия имеет значение null, это не влияет на значение оригинала a2.

Ответ 3

Для a2 исходного референта это фактически полностью зависит от того, что происходит в "некотором другом коде". Если "какой-либо другой код" не использует a2 или a3, тогда исходный объект a2 имеет право на сбор мусора.

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

Ответ 4

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

Неверно говорить о переменных, таких как b и a2 как собираемый мусор. Объекты собираются мусором, а не переменными. Если переменная в области видимости ссылается на объект, то она не может быть собрана в мусор. Упростительно, это только тогда, когда объект больше не ссылается на какую-либо переменную, которую может собирать мусор.

Итак, у нас есть три экземпляра A, созданных в этом коде. Они начинаются с ссылки a1 и т.д., Но поскольку переменные ссылки меняются, я буду ссылаться на экземпляры объектов как A1, A2 и A3. Так как вы не указали определение метода go, я собираюсь предположить, что это вызов для easyMethod.

Так как переменная a1 переназначена в null, ничто не указывает на экземпляр A1, поэтому он может быть собран с помощью мусора.

Так как переменная a2 никогда не переназначается (присвоение в easyMethod не влияет на исходную переменную), экземпляр A2 не может быть собран в мусор.

Так как easyMethod всегда возвращает null, а a3 присваивается результат этого метода, ничто не указывает на экземпляр A3, поэтому он также может быть собран в мусор.

Ответ 5

Не могли бы вы объяснить мне, почему a2 и a3 не собираются мусором

Потому что a2 и a3 не являются объектами. Они являются переменными. Переменные не могут быть собраны по той простой причине, что они не подлежат распределению.

Ответ 6

Вопрос в том, сколько объектов имеет право на сбор мусора прямо перед // Some other code.

Вопрос бессмыслен.

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

Итак, мы должны сначала принять прямое соответствие между исходным кодом и сгенерированным кодом, или мы даже не можем понять смысл кода, на который ссылается этот вопрос. Я не могу думать о какой-либо рабочей VM, которая на самом деле делает это на практике, и на самом деле Java, вероятно, имеет языковые конструкции высокого уровня, которые невозможно даже скомпилировать для байт-кода таким образом.

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

Рассмотрим простую модель, в которой промежуточные значения (результаты всех подвыражений, а также переменные) помещаются в стек до конца функции. Простые компиляторы и виртуальные машины, такие как HLVM и .NET на Windows Phone 7, фактически работают так на практике, хотя это сохраняет ссылки дольше, чем это необходимо. С помощью этой модели каждый new A() подталкивает ссылку на стек до тех пор, пока функция не вернется, так что все три доступны из стека в рассматриваемой точке и, следовательно, ничто не имеет права на сбор мусора.

Рассмотрим модель, в которой ссылки удаляются из стека в первой точке, где они никогда не читаются снова. Это ближе к тому, как работают производственные виртуальные машины, такие как .NET и OCaml, за исключением того, что они сохраняют локальные ссылки в регистрах, когда это возможно, и разливаются в предварительно выделенные записи в фрейме стека функций, иначе перезаписывая мертвые локали, чтобы минимизировать размер фрейма стека. С этой моделью все ссылки мертвы в рассматриваемой точке, поэтому никто не достигнут, и поэтому все три вновь выделенных объекта имеют право на сбор мусора, а также массив args и все строки, на которые он ссылается.

Таким образом, мы не только показали, что ответы от nothing до everything могут быть оправданы, но мы даже указали реальные рабочие виртуальные машины и сборщики мусора, которые реализуют эти модели, поэтому наши прогнозы можно проверить.

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

Ответ 7

Ваш вопрос довольно запутан.

Сборщик мусора работает с объектами, а не с переменными. Поэтому, когда вы говорите, что a2 имеет право на GC, это ничего не значит. Вы должны сказать, что объект, на который ссылается a2 в строке N, имеет право на GC на линии N + M.

В вашем примере вы создаете только три объекта. Это первый создатель A и последний экземпляр экземпляра, который подходит для GC.