Почему объект, объявленный в методе, подлежит сборке мусора перед возвратом метода? - PullRequest
5 голосов
/ 20 марта 2010

Рассмотрим объект, объявленный в методе:

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

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

Будет ли объект подвергаться сборке мусора до того, как foo () вернется?

UPDATE : Ранее я думал, что obj не подлежит сборке мусора , пока не вернется foo ().

Однако сегодня я ошибаюсь.

Я потратил несколько часов на исправление ошибки и, наконец, обнаружил, что проблема вызвана сборкой мусора 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;
            }
        }
    }
}

Ответы [ 7 ]

5 голосов
/ 20 марта 2010

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

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

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

3 голосов
/ 20 марта 2010

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

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

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

к этому:

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

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

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

2 голосов
/ 20 марта 2010

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

Но да, сам Object будет очищен сборщиком мусора.Все объекты, выделенные в куче, подчиняются GC.

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

РЕДАКТИРОВАТЬ: Пояснения, по комментариям

1 голос
/ 14 августа 2012

Будет ли объект подвергаться сборке мусора до того, как foo () вернется?

Вы не можете быть уверены, что obj будет собираться до foo возврата, но он, безусловно, подходит для сборадо того, как foo вернется.

Кто-нибудь может объяснить, почему это происходит?

ГК собирают недоступные объекты.Ваш объект, вероятно, станет недоступным до того, как foo вернется.

Область действия не имеет значения.Идея о том, что obj остается в стеке до возвращения foo, является чрезмерно упрощенной ментальной моделью.Реальные системы так не работают.

1 голос
/ 20 марта 2010

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

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

0 голосов
/ 20 марта 2010

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

В редких случаях вам действительно нужно написать финализатор.

0 голосов
/ 20 марта 2010

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


EDIT: Чтобы ответить на ваше обновление,

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

obj - это просто ссылка на фактический объект в куче . Здесь obj объявлено внутри метода foo (). Таким образом, ваш вопрос Will obj be subject to garbage collection before foo() returns? не применяется, поскольку obj входит в кадр стека, когда метод foo() работает, и исчезает, когда метод завершается.

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