Почему объект, объявленный в методе, подвергается сборке "мусора" перед возвратами метода?

Считайте объект объявленным в методе:

public void foo() {
    final Object obj = new Object();

    // A long run job that consumes tons of memory and 
    // triggers garbage collection
}

obj подвергнется сборке "мусора" перед нечто () возвраты?

ОБНОВЛЕНИЕ: Ранее я думал, что obj не подвергается сборке "мусора" до нечто () возвраты.

Однако сегодня я оказываюсь неправильно.

Я имею, проводят несколько часов в исправлении ошибки и наконец нашел, что проблема вызывается собравшим "мусор" obj!

Кто-либо может объяснить, почему это происходит? И если я хочу, чтобы obj был прикреплен, как достигнуть его?

Вот код, который имеет проблему.

public class Program
{
    public static void main(String[] args) throws Exception {
        String connectionString = "jdbc:mysql://<whatever>";

        // I find wrap is gc-ed somewhere
        SqlConnection wrap = new SqlConnection(connectionString); 

        Connection con = wrap.currentConnection();
        Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, 
             ResultSet.CONCUR_READ_ONLY);
        stmt.setFetchSize(Integer.MIN_VALUE);

        ResultSet rs = stmt.executeQuery("select instance_id, doc_id from
               crawler_archive.documents");

        while (rs.next()) {
            int instanceID = rs.getInt(1);
            int docID = rs.getInt(2);

            if (docID % 1000 == 0) {
                System.out.println(docID);
            }
        }

        rs.close();
        //wrap.close();
    }
}

После запущения программы Java это распечатает следующее сообщение, прежде чем это откажет:

161000
161000
********************************
Finalizer CALLED!!
********************************
********************************
Close CALLED!!
********************************
162000
Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: 

И вот является код класса SqlConnection:

class SqlConnection
{
    private final String connectionString;
    private Connection connection;

    public SqlConnection(String connectionString) {
        this.connectionString = connectionString;
    }

    public synchronized Connection currentConnection() throws SQLException {
        if (this.connection == null || this.connection.isClosed()) {
            this.closeConnection();
            this.connection = DriverManager.getConnection(connectionString);
        }
        return this.connection;
    }

    protected void finalize() throws Throwable {
        try {
            System.out.println("********************************");
            System.out.println("Finalizer CALLED!!");
            System.out.println("********************************");
            this.close();
        } finally {
            super.finalize();
        }
    }

    public void close() {
        System.out.println("********************************");
        System.out.println("Close CALLED!!");
        System.out.println("********************************");
        this.closeConnection();
    }

    protected void closeConnection() {
        if (this.connection != null) {
            try {
                connection.close();
            } catch (Throwable e) {
            } finally {
                this.connection = null;
            }
        }
    }
}
5
задан SiLent SoNG 20 March 2010 в 10:17
поделиться

6 ответов

Поскольку ваш код написан , объект, на который указывает «wrap», не должен иметь право на сборку мусора, пока «wrap» не появится из стека в конце метода.

Тот факт, что он собирается, подсказывает мне, что ваш код , скомпилированный , не соответствует исходному источнику, и что компилятор провел некоторую оптимизацию, например, изменив это:

SqlConnection wrap = new SqlConnection(connectionString); 
Connection con = wrap.currentConnection();

на следующее:

Connection con = new SqlConnection(connectionString).currentConnection();

(или даже встраивание всего этого), потому что за пределами этого пункта термин «перенос» не используется. Созданный анонимный объект сразу получит право на сборку мусора.

Единственный способ быть уверенным - это декомпилировать код и посмотреть, что с ним сделали.

3
ответ дан 13 December 2019 в 05:33
поделиться

Здесь obj - это локальная переменная в методе, которая извлекается из стека, как только метод возвращается или завершается. Это не оставляет возможности достичь объекта Object в куче, и, следовательно, он будет собран сборщиком мусора. А объект Object в куче будет GC'd только после того, как его ссылка obj будет извлечена из стека, то есть только после того, как метод завершится или вернется.


РЕДАКТИРОВАТЬ: Чтобы ответить на ваше обновление,

UPDATE: Let me make the question more clear. 
Will obj be subject to garbage collection before foo() returns?

obj - это просто ссылка на фактический объект на куча . Здесь obj объявлен внутри метода foo (). Итак, ваш вопрос Будет ли obj подвергаться сборке мусора до того, как foo () вернется? не применяется, поскольку obj входит в кадр стека, когда метод foo () является выполняется и исчезает, когда метод завершается.

0
ответ дан 13 December 2019 в 05:33
поделиться

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

Вряд ли вам действительно понадобится написать финализатор.

0
ответ дан 13 December 2019 в 05:33
поделиться

На самом деле здесь происходят две разные вещи. obj - это переменная стека, для которой устанавливается ссылка на объект , а объект размещается в куче. Стек будет просто очищен (арифметикой указателя стека).

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

РЕДАКТИРОВАТЬ: Чтобы ответить на ваш более конкретный вопрос, спецификация Java не гарантирует сбор по в любое конкретное время (см. спецификацию JVM ) (конечно, он будет собран ] после его последнего использования). Так что ответить можно только для конкретных реализаций.

РЕДАКТИРОВАТЬ: Уточнение, в комментариях

2
ответ дан 13 December 2019 в 05:33
поделиться

Как я уверен, вы знаете, в Java сборка мусора и финализация не являются детерминированными. Все, что вы можете определить в этом случае, - это когда wrap является подходящим для сборки мусора. Я предполагаю, что вы спрашиваете, становится ли wrap подходящим для GC только тогда, когда метод возвращает (а wrap выходит за рамки). Я думаю, что некоторые JVM (например,HotSpot с -сервер ) не будет ждать, пока ссылка на объект будет извлечена из стека, он сделает его подходящим для GC, как только на него больше ничего не ссылается. Похоже, это то, что вы видите.

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

1
ответ дан 13 December 2019 в 05:33
поделиться

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

public class GcTest {

    public static void main(String[] args) {
        System.out.println("Starting");

        Object dummy = new GcTest(); // gets GC'd before method exits

        // gets bigger and bigger until heap explodes
        Collection<String> collection = new ArrayList<String>();

        // method never exits normally because of while loop
        while (true) {
            collection.add(new String("test"));
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizing instance of GcTest");
    }
}

Работает с:

Starting
Finalizing instance of GcTest
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at test.GcTest.main(GcTest.java:22)

Как я уже сказал, мне трудно в это поверить, но отрицать доказательства невозможно.

Правда, в этом есть извращенный смысл: ВМ выясняет, что объект никогда не используется, и избавляется от него. Это должно быть разрешено спецификацией.

Возвращаясь к коду вопроса, вы никогда не должны полагаться на finalize() для очистки ваших соединений, вы должны всегда делать это явно.

5
ответ дан 13 December 2019 в 05:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: