Когда локальная переменная Java подходит для GC? - PullRequest
5 голосов
/ 01 сентября 2009

Учитывая следующую программу:

import java.io.*;
import java.util.*;

public class GCTest {

    public static void main(String[] args) throws Exception {
        List cache = new ArrayList();
        while (true) {
            cache.add(new GCTest().run());
            System.out.println("done");
        }
    }

    private byte[] run() throws IOException {
        Test test = new Test();
        InputStream is = test.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buff = new byte[256];
        int len = 0;
        while (-1 != (len = is.read())) {
            baos.write(buff, 0, len);
        }
        return baos.toByteArray();
    }

    private class Test {
        private InputStream is;

        public InputStream getInputStream() throws FileNotFoundException {
            is = new FileInputStream("GCTest.class");
            return is;
        }

        protected void finalize() throws IOException {
            System.out.println("finalize");
            is.close();
            is = null;
        }
    }
}

Вы ожидаете, что финализ будет когда-либо вызываться, когда цикл while в методе run все еще выполняется и проверка локальной переменной все еще находится в области видимости?

Что более важно, это поведение определено где-нибудь? Есть ли что-нибудь от Sun, утверждающее, что это определяется реализацией?

Это своего рода обратный способ, которым этот вопрос задавался ранее на SO, где люди в основном озабочены утечками памяти. Здесь у нас GC настойчиво GCing переменная, в которой мы все еще заинтересованы. Вы можете ожидать, что, поскольку test все еще находится в области действия, это не будет GC'd.

Для записи, кажется, что иногда тест "работает" (то есть, в конечном итоге попадает в OOM), а иногда он не проходит, в зависимости от реализации JVM.

Не защищая способ написания этого кода, кстати, это просто вопрос, который возник на работе.

Ответы [ 6 ]

14 голосов
/ 01 сентября 2009

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

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

Редактировать: Здесь - это явная документация, которую вы ищете.

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

Это не может быть предположено. Ни Спецификация Java, ни спецификация JVM не гарантируют это.

Только потому, что переменная находится в области видимости, не означает, что объект указывает на достижимо Обычно это так что объект, на который указывает переменная в области видимости доступна, но ваш случай, когда это не так. Компилятор может определить во время Jit какие переменные мертвы и не включить такие переменные в oop-карту. Поскольку объект указывает на «нт» может [так - должно быть не может] быть достигается из любой живой переменной, это право на сбор.

8 голосов
/ 02 сентября 2009

Я рекомендую вам и вашему коллеге прочитать Правду о сборке мусора .

В самом начале написано следующее:

Спецификация для Java Платформа дает очень мало обещаний о как на самом деле работает сборка мусора. [Опущено]

Хотя это может показаться запутанным, факт что модель сборки мусора не жестко определено на самом деле важный и полезный - строго определенный модель сбора мусора может быть невозможно реализовать на всех платформ. Точно так же это может исключить полезные оптимизации и вред производительность платформы в долгосрочный.

В вашем примере переменная test становится "невидимой" (см. A.3.3 выше) в цикле while. На этом этапе некоторые JVM продолжат рассматривать переменную как содержащую «жесткую ссылку», а другие JVM будут обрабатывать ее так, как если бы переменная была обнулена. Любое поведение приемлемо для совместимой JVM

Цитирование из издания 3 JLS (раздел 12.6.1, абзац 2):

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

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

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

(Мое выделение добавлено.) Это означает, что объектный объект может быть собран мусором, и финализация может произойти раньше или позже, чем вы ожидаете. Стоит также отметить, что некоторым JVM требуется более одного цикла GC, чтобы завершить работу с недоступными объектами.

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

3 голосов
/ 02 сентября 2009

Немного не по теме, но finalize () никогда не следует использовать для закрытия () файла. Язык не гарантирует, что finalize () когда-либо будет вызван. Всегда используйте конструкцию try ... finally, чтобы гарантировать закрытие файла, очистку базы данных и т. Д.

1 голос
/ 01 сентября 2009

Что вы наблюдаете, что находите странным? Каждый раз, когда вы выполняете run (), вы создаете новый экземпляр Test. По завершении выполнения этот экземпляр теста выходит за пределы области и может быть использован для сборки мусора. Конечно, «право на сбор мусора» и «сбор мусора» - это не одно и то же. Я ожидаю, что если вы запустите эту программу, вы увидите множество прокручиваемых сообщений, которые будут прокручиваться как вызовы run complete. Поскольку я вижу только консольный вывод - эти сообщения, я не вижу, как вы узнали бы, какой экземпляр Test завершается, когда вы видите каждое сообщение. Вы можете получить более интересные результаты, если добавляете println в начале каждого вызова run и, возможно, даже добавляете счетчик к объекту Test, который увеличивается каждый раз при создании нового объекта и выводится вместе с сообщением финализации. Тогда вы могли видеть, что на самом деле происходит. (Ну, может быть, вы запускаете это с отладчиком, но это также может затенить больше.)

0 голосов
/ 01 сентября 2009

Теоретически Test не должен находиться в области видимости, поскольку он находится на уровне метода run (), а локальные переменные должны собираться мусором по мере выхода из метода. Однако, если вы сохраняете результаты в списке, и у меня есть Прочтите это где-нибудь, чтобы списки были склонны хранить слабые ссылки, которые нелегко собирать (в зависимости от реализации jvm).

0 голосов
/ 01 сентября 2009

Поскольку test используется только один раз, его можно удалить сразу после обращения к нему. Даже если каждый вызов read использует вызов getInputStream вместо использования локальной переменной is, использование объекта может быть оптимизировано. FIleInputStream невозможно завершить преждевременно из-за использования блокировки. Финалисты сложны.

В любом случае ваш финализатор бессмысленен. Базовый FileInputStream все равно закроется при финализации.

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