Domanda

Stavo leggendo Prestazioni della piattaforma Java (purtroppo il collegamento sembra essere scomparso da Internet da quando ho posto inizialmente questa domanda) e la sezione A.3.3 mi ha preoccupato.

Avevo lavorato sul presupposto che una variabile che non rientrava nell'ambito di applicazione non sarebbe più stata considerata una radice GC, ma questo documento sembra contraddirlo.

Le recenti JVM, in particolare la versione 1.6.0_07 di Sun, hanno ancora questa limitazione? In tal caso, ho un sacco di codice da analizzare ...

Pongo la domanda perché l'articolo è del 1999 - a volte le cose cambiano, in particolare nel mondo di GC.


Dato che il documento non è più disponibile, vorrei parafrasare la preoccupazione. Il documento implicava che le variabili definite all'interno di un metodo sarebbero state considerate una radice GC fino alla fine del metodo e non fino alla fine del blocco di codice. Pertanto, impostare la variabile su null era necessario per consentire che l'oggetto a cui si fa riferimento fosse garbage collection.

Ciò significava che una variabile locale definita in un blocco condizionale nel metodo main () (o metodo simile che conteneva un ciclo infinito) avrebbe causato una perdita di memoria una tantum a meno che non si annullasse una variabile appena prima che cadesse dall'ambito .

Il codice dalla risposta scelta illustra bene il problema. Sulla versione della JVM a cui si fa riferimento nel documento, l'oggetto foo non può essere garbage collection quando esce dall'ambito alla fine del blocco try. Invece, la JVM manterrà aperto il riferimento fino alla fine del metodo main (), anche se è impossibile per qualsiasi cosa utilizzare quel riferimento.

Questa sembra essere l'origine dell'idea che l'annullamento di un riferimento di variabile aiuterebbe il garbage collector a uscire, anche se la variabile stava per abbandonare l'ambito.

È stato utile?

Soluzione

Questo codice dovrebbe chiarire:

public class TestInvisibleObject{
  public static class PrintWhenFinalized{
    private String s;
    public PrintWhenFinalized(String s){
      System.out.println("Constructing from "+s);
      this.s = s;
    }
    protected void finalize() throws Throwable {
      System.out.println("Finalizing from "+s);
    }   
  }
  public static void main(String[] args) {
    try {
        PrintWhenFinalized foo = new PrintWhenFinalized("main");
    } catch (Exception e) {
        // whatever
    }
    while (true) {
      // Provoke garbage-collection by allocating lots of memory
      byte[] o = new byte[1024];
    } 
  }
}

Sulla mia macchina (jdk1.6.0_05) stampa:

  

Costruzione dal principale

     

Finalizzazione dal principale

Quindi sembra che i problemi siano stati risolti.

Nota che l'uso di System.gc () invece del ciclo non causa la raccolta dell'oggetto per qualche motivo.

Altri suggerimenti

L'articolo afferma che:

  

... un'implementazione efficiente di   È improbabile che JVM azzeri il riferimento   quando esce dal campo di applicazione

Penso che ciò accada a causa di situazioni come questa:

public void doSomething() {  
    for(int i = 0; i < 10 ; i++) {
       String s = new String("boo");
       System.out.println(s);
    }
}

Qui, lo stesso riferimento viene utilizzato dalla "efficiente JVM" in ogni dichiarazione di String s, ma ci saranno 10 nuove stringhe nell'heap se il GC non si avvia.

Nell'esempio dell'articolo penso che il riferimento a foo rimanga nello stack perché il "efficiente JVM" pensa che è molto probabile che verrà creato un altro oggetto foo e, in tal caso, utilizzerà lo stesso riferimento. Pensieri ???

public void run() {
    try {
        Object foo = new Object();
        foo.doSomething();
    } catch (Exception e) {
        // whatever
    }
    while (true) { // do stuff } // loop forever
}

Ho anche eseguito il prossimo test con la profilazione:

public class A {

    public static void main(String[] args) {
        A a = new A();  
        a.test4();
    }

    public void test1() {  
        for(int i = 0; i < 10 ; i++) {
           B b = new B();
           System.out.println(b.toString());
        }
        System.out.println("b is collected");
    }

    public void test2() {
        try {
            B b = new B();
            System.out.println(b.toString());
        } catch (Exception e) {
        }
        System.out.println("b is invisible");
    }

    public void test3() {
        if (true) {
            B b = new B();
            System.out.println(b.toString());
        }
        System.out.println("b is invisible");
    }

    public void test4() {
        int i = 0;
        while (i < 10) {
            B b = new B();
            System.out.println(b.toString());
            i++;
        }
        System.out.println("b is collected");
    }

    public A() {
    }

    class B {
        public B() {
        }

        @Override
        public String toString() {
            return "I'm B.";
        }
    }
}

e vieni alle conclusioni:

teste1 - > b è raccolto

teste2 - > b è invisibile

teste3 - > b è invisibile

teste4 - > b è raccolto

... quindi penso che, nei loop, la JVM non crei variabili invisibili quando il ciclo termina perché è improbabile che vengano dichiarati di nuovo al di fuori del ciclo.

Eventuali pensieri ??

Avresti davvero così tanto codice da analizzare? Fondamentalmente posso vedere che questo è un problema significativo per i metodi a esecuzione molto lunga, che sono in genere solo quelli in cima allo stack di ogni thread.

Non sarei affatto sorpreso se al momento non è stato risolto, ma non penso che sia probabilmente così significativo come sembra temere.

Il problema è ancora presente. L'ho provato con Java & nbsp; 8 e ho potuto dimostrarlo.

Dovresti notare le seguenti cose:

  1. L'unico modo per forzare una garbage collection garantita è provare un'allocazione che termina in OutOfMemoryError poiché JVM è necessario per provare a liberare oggetti inutilizzati prima di lanciare. Ciò tuttavia non vale se l'importo richiesto è troppo elevato per riuscire, ovvero eccede lo spazio degli indirizzi. Cercare di aumentare l'allocazione fino a ottenere un OOME è una buona strategia.

  2. Il GC garantito descritto al punto 1 non garantisce una finalizzazione. Il momento in cui vengono invocati i metodi finalize () non viene specificato, potrebbero non essere mai chiamati affatto. Quindi l'aggiunta di un metodo finalize () a una classe potrebbe impedire la raccolta delle sue istanze, quindi finalize non è una buona scelta per analizzare il comportamento di GC.

  3. La creazione di un'altra nuova variabile locale dopo che una variabile locale è uscita dall'ambito riutilizzerà il suo posto nel frame dello stack. Nel seguente esempio, l'oggetto a verrà raccolto poiché il suo posto nel frame dello stack viene occupato dalla variabile locale b. Ma durano fino alla fine del metodo principale in quanto non vi è altra variabile locale che occupi il suo posto.

    import java.lang.ref.*;
    
    public class Test {
        static final ReferenceQueue<Object> RQ=new ReferenceQueue<>();
        static Reference<Object> A, B;
        public static void main(String[] s) {
            {
                Object a=new Object();
                A=new PhantomReference<>(a, RQ);
            }
            {
                Object b=new Object();
                B=new PhantomReference<>(b, RQ);
            }
            forceGC();
            checkGC();
        }
    
        private static void forceGC() {
            try {
                for(int i=100000;;i+=i) {
                  byte[] b=new byte[i];
                }
            } catch(OutOfMemoryError err){ err.printStackTrace();}
        }
    
        private static void checkGC() {
            for(;;) {
                Reference<?> r=RQ.poll();
                if(r==null) break;
                if(r==A) System.out.println("Object a collected");
                if(r==B) System.out.println("Object b collected");
            }
        }
    }
    
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top