2010-03-20 7 views
5

consideri un oggetto dichiarato in un metodo:Perché un oggetto dichiarato nel metodo è soggetto alla garbage collection prima che il metodo restituisca?

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

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

Sarà obj essere oggetto di garbage collection prima del ritorno di foo()?

UPDATE: precedenza ho pensato obj non è soggetto a garbage collection fino al ritorno foo().

Tuttavia, oggi mi sono sbagliato.

Ho passato diverse ore a correggere un bug e finalmente ho scoperto che il problema è causato da obj garbage collection!

Qualcuno può spiegare perché questo accade? E se voglio che obj venga bloccato come ottenerlo?

Ecco il codice che presenta problemi.

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(); 
    } 
} 

Dopo aver eseguito il programma Java, verrà stampata il seguente messaggio prima che si schianti:

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

Ed ecco il codice della classe 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; 
      } 
     } 
    } 
} 
+1

Non stai stampando il messaggio in 'finalize()', lo stai stampando in 'close()', e stai chiamando 'close()' te stesso. Sposta il messaggio nel finalizzatore stesso e riprova. – skaffman

+1

Dovresti pubblicare una domanda a parte per il tuo aggiornamento. –

+0

@skaffman: dovrei rimuovere il close() dal main. Il problema persiste ancora. –

risposta

3

Dato che il codice è scritto, l'oggetto indicato da "wrap" non dovrebbe essere idoneo per la garbage collection finché "wrap" non si stacca dallo stack alla fine del metodo.

Il fatto che vengono raccolti mi suggerisce il codice come compilato non corrisponde alla fonte originale e che il compilatore ha fatto qualche ottimizzazione come cambiare questo:

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

a questo:

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

(O anche inlining dell'intera cosa) perché "wrap" non è usato oltre questo punto. L'oggetto anonimo creato sarebbe idoneo per GC immediatamente.

L'unico modo per essere sicuri è decompilare il codice e vedere cosa è stato fatto.

+0

Trovo che questa sia la causa principale del problema. –

0

Qui, obj è una variabile locale nel metodo e viene estratta dallo stack non appena il metodo restituisce o esce. Ciò non consente in alcun modo di raggiungere l'oggetto Object nell'heap e pertanto verrà raccolto. E l'oggetto Object nell'heap sarà GC solo dopo che il suo riferimento obj viene estratto dallo stack, ovvero solo dopo il completamento o la restituzione del metodo.


EDIT: di rispondere alle vostre aggiornamento,

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

obj è solo un riferimento al oggetto reale sul mucchio. Qui obj è dichiarato all'interno del metodo foo(). Quindi la tua domanda Will obj be subject to garbage collection before foo() returns? non si applica come obj entra nel frame dello stack quando il metodo foo() è in esecuzione e scompare quando termina il metodo.

+0

obj è un tipo di riferimento, quindi solo il riferimento (il puntatore) è memorizzato nello stack, i dati effettivi sono nell'heap. –

+0

@Simon P Stevens, questo è esattamente quello che ho menzionato. La variabile di riferimento obj è una VARIABLE LOCALE. – Zaki

+0

Siamo spiacenti. Non ho letto i tag. Pensando a .net. (Ho modificato in modo da poter revocare il mio downvote). Scusate. –

2

Ci sono davvero due cose diverse che accadono qui. obj è una variabile di stack impostata su un riferimento allo Object e lo Object è allocato nello heap. Lo stack verrà semplicemente cancellato (mediante l'aritmetica del puntatore dello stack).

Ma sì, lo stesso Object verrà cancellato dalla procedura di garbage collection. Tutti gli oggetti allocati su heap sono soggetti a GC.

EDIT: Per rispondere alla tua domanda più specifica, la specifica Java non garantisce la raccolta da un determinato momento (vedi the JVM spec) (ovviamente saranno raccolti dopo suo ultimo impiego). Quindi è possibile rispondere solo per implementazioni specifiche.

EDIT: Precisazione, per i commenti

+1

Non garantisce un tempo, MA questo non significa che raccoglierà in modo casuale il tuo oggetto ogni volta anche se è ancora in ambito;) – Paolo

+2

@Paolo - il problema qui è che è fuori dall'ambito dopo l'ultima volta che viene utilizzato (non quando cade dalla pila) –

+0

E l'optmisation potrebbe rendere sorprendente il tempo dell'ultimo utilizzo. –

1

Come io sono sicuro che siete a conoscenza, in Java Garbage Collection e Finialization sei non-deterministico. Tutto ciò che è possibile determinare in questo caso è quando wrap è idoneo per la garbage collection. Suppongo che tu stia chiedendo se lo wrap diventi idoneo per GC solo quando il metodo restituisce (e wrap esce dal campo di applicazione). Penso che alcune JVM (ad esempio HotSpot con -server) non attenderanno il prelievo dell'oggetto dallo stack, lo renderà idoneo per GC non appena ne farà riferimento. Sembra che questo sia quello che stai vedendo.

Per riepilogare, si sta facendo affidamento sul fatto che la finalizzazione sia abbastanza lenta da non finalizzare l'istanza di SqlConnection prima che il metodo venga chiuso. Il finalizzatore sta chiudendo una risorsa per cui lo SqlConnection non è più responsabile. Invece, dovresti lasciare che l'oggetto Connection sia responsabile della sua stessa finalizzazione.

+0

@Ben Lings: in realtà il metodo close() reso pubblico è quello di consentire all'utente di disporre esplicitamente dell'oggetto. Tuttavia, se l'utente dimentica di chiudere() l'oggetto, il finalizzatore funge da ultima guardia per rilasciare la risorsa. Questo è molto simile al modello di progettazione di smaltimento .NET. –

+0

Il problema è che il tuo finalizzatore sta pulendo una risorsa di cui non è responsabile. –

5

Sono sinceramente sbalordito da questo, ma hai ragione. E 'facilmente riproducibile, non è necessario fare muck circa con connessioni al database e simili:

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"); 
    } 
} 

Funziona con:

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) 

Come ho detto, faccio fatica a crederci, ma non si può negare la prova.

Ha un senso perverso di senso, tuttavia, la VM avrà capito che l'oggetto non è mai stato utilizzato, e quindi si sbarazza di esso. Questo deve essere consentito dalle specifiche.

Tornando al codice della domanda, non si dovrebbe mai fare affidamento su finalize() per pulire le connessioni, è necessario sempre farlo esplicitamente.

+1

wow, grande intuizione. – Zaki

0

In base alle specifiche attuali, non si verifica nemmeno un , prima dell'ordinazione da finalizzazione all'utilizzo normale. Quindi, per imporre l'ordine, è effettivamente necessario utilizzare un blocco, un volatile o, se si è disperati, la memorizzazione di un riferimento raggiungibile da una statica. Non c'è certamente nulla di speciale riguardo alla portata.

Dovrebbe essere raro che sia effettivamente necessario scrivere un finalizzatore.

1

Sarà soggetto a garbage collection prima che foo() restituisca?

Non si può essere sicuri obj saranno raccolti prima foo rendimenti ma è certamente idoneo per la raccolta prima di foo ritorni.

Qualcuno può spiegare perché questo accade?

I GC raccolgono oggetti non raggiungibili. È probabile che il tuo oggetto diventi irraggiungibile prima dei resi di foo.

La portata è irrilevante. L'idea che obj resti in pila fino al foo resi è un modello mentale eccessivamente semplicistico. I sistemi reali non funzionano così.

Problemi correlati