2010-05-11 13 views
7

Possiedo un JFrame che accetta gocce di file di livello superiore. Tuttavia, dopo che si è verificata una caduta, i riferimenti al frame vengono mantenuti indefinitamente all'interno di alcune classi interne di Swing. Credo che lo smaltimento della cornice dovrebbe liberare tutte le sue risorse, quindi cosa sto facendo di sbagliato?Perdita di memoria con Swing Drag and Drop

Esempio

import java.awt.datatransfer.DataFlavor; 
import java.io.File; 
import java.util.List; 

import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.TransferHandler; 

public class DnDLeakTester extends JFrame { 
    public static void main(String[] args) { 
     new DnDLeakTester(); 

     //Prevent main from returning or the jvm will exit 
     while (true) { 
      try { 
       Thread.sleep(10000); 
      } catch (InterruptedException e) { 

      } 
     } 
    } 
    public DnDLeakTester() { 
     super("I'm leaky"); 

     add(new JLabel("Drop stuff here")); 

     setTransferHandler(new TransferHandler() { 
      @Override 
      public boolean canImport(final TransferSupport support) { 
       return (support.isDrop() && support 
         .isDataFlavorSupported(DataFlavor.javaFileListFlavor)); 
      } 

      @Override 
      public boolean importData(final TransferSupport support) { 
       if (!canImport(support)) { 
        return false; 
       } 

       try { 
        final List<File> files = (List<File>) 
          support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); 

        for (final File f : files) { 
         System.out.println(f.getName()); 
        } 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 

       return true; 
      } 
     }); 

     setDefaultCloseOperation(DISPOSE_ON_CLOSE); 
     pack(); 
     setVisible(true); 
    } 
} 

Per riprodurre, eseguire il codice e rilasciare alcuni file sul telaio. Chiudi la cornice in modo che sia eliminata.

Per verificare la perdita, prendo un dump dell'heap utilizzando JConsole e analizzandolo con Eclipse Memory Analysis tool. Mostra che sun.awt.AppContext ha un riferimento al frame tramite la sua hashmap. Sembra che TransferSupport sia in errore.

image of path to GC root http://img402.imageshack.us/img402/4444/dndleak.png

Che cosa sto facendo di sbagliato? Dovrei chiedere al codice di supporto DnD di pulirsi in qualche modo?

Io corro JDK 1.6 update 19.

+0

Sto iniziando a pensare che si tratti di un bug JVM. Le classi DnD rilevanti non hanno il codice per cancellare i riferimenti incriminati, quindi a meno che DropHandler non sia rimosso dalla mappa di AppContext in qualche modo (non capisco davvero la classe), la perdita rimarrà. – tom

+0

Questo post sul forum descrive un problema simile [http://forums.java.net/jive/thread.jspa?messageID=276311]. Non ha ricevuto risposte. – tom

risposta

4

Sebbene DropHandler non venga rimosso dalla mappa statica di AppContext, non è realmente la causa principale, ma solo una delle cause della catena. (Il gestore di drop è concepito come un singleton e non cancellato fino a quando la classe AppContext non viene scaricata, cosa che in pratica non è mai.) L'uso di un singolo DropHandler è di progettazione.

La vera causa della perdita è che DropHandler imposta un'istanza di TransferSupport, che viene riutilizzata per ogni operazione DnD, e durante un'operazione DnD, fornisce un riferimento al componente coinvolto in DnD. Il problema è che non cancella il riferimento quando DnD termina. Se DropHandler ha chiamato TransferSupport.setDNDVariables(null,null) quando si è chiuso il DnD, il problema sarebbe scomparso. Questa è anche la soluzione più logica, dal momento che il riferimento al componente è richiesto solo mentre è in corso DnD. Altri approcci, come la cancellazione della mappa AppContext, stanno aggirando il progetto piuttosto che fissare una piccola supervisione.

Ma anche se lo aggiustiamo, la cornice non verrebbe ancora raccolta.Sfortunatamente, sembra esserci un altro problema: quando ho commentato tutto il codice relativo a DnD, riducendo a un semplice JFrame, anche questo non veniva raccolto. Il riferimento di retaning era javax.swing.BufferStrategyPaintManager. C'è uno bug report per questo, non ancora risolto.

Quindi, se ripariamo il DnD, abbiamo riscontrato un altro problema di ritenzione con la riverniciatura. Fortunatamente, tutti questi bug mantengono solo un frame (si spera lo stesso!), Quindi non è così male come potrebbe essere. Il frame è disposto, quindi vengono rilasciate le risorse native e tutto il contenuto può essere rimosso, consentendo di liberarlo, riducendo la gravità della perdita di memoria.

Quindi, per rispondere alla tua domanda, non stai facendo nulla di sbagliato, stai solo dando alcuni dei bug nel JDK un po 'di tempo!

UPDATE: Il ridisegno direttore di bug ha una soluzione rapida - aggiungendo

-Dswing.bufferPerWindow=false 

Per le opzioni di avvio JVM evita il bug. Con questo bug annullato, ha senso pubblicare una correzione per il bug DnD:

Per risolvere il problema di DnD, è possibile aggiungere una chiamata a questo metodo alla fine di importData().

  private void cancelDnD(TransferSupport support) 
      { 
       /*TransferSupport.setDNDVariables(Component component, DropTargetEvent event) 
       Call setDNDVariables(null, null) to free the component. 
*/ 
       try 
       { 
        Method m = support.getClass().getDeclaredMethod("setDNDVariables", new Class[] { Component.class, DropTargetEvent.class }); 
        m.setAccessible(true); 
        m.invoke(support, null, null); 
        System.out.println("cancelledDnd"); 
       } 
       catch (Exception e) 
       { 
       } 
      } 
+0

Grazie per le informazioni. Avevo notato BufferStrategyPaintManager che manteneva alcune classi, ma era distratto dalla roba del DnD. Accetto che il trucco riflesso funzioni - ma non intendo usarlo – tom

+0

Certo, è una tua scelta se usi la correzione o meno, ma ora almeno conosci la portata del problema e puoi decidere come trattarla esso. Come te. Probabilmente non mi preoccuperei di aggiungere la correzione, e sapendo che il problema può essere parzialmente mitigato, probabilmente non fare nulla o, al massimo, rimuovere il contenuto dai frame a disposizione. Ma con il senno di poi ora si conosce la portata del problema. Ero molto curioso di sapere perché la perdita stava accadendo e quali erano le conseguenze, ed è bello sapere che se improvvisamente troviamo una correzione è necessaria, quella è disponibile. Posso dormire sonni tranquilli! :-) – mdma

1

Lo fa cambiare i risultati se si aggiunge questo alla vostra classe?

@Override 
public void dispose() 
{ 
    setTransferHandler(null); 
    setDropTarget(null); // New 
    super.dispose(); 
} 

Aggiornamento: ho aggiunto un'altra chiamata al Dispose. Penso che l'impostazione del drop target su null dovrebbe rilasciare qualche riferimento in più. Non vedo nient'altro sul componente che può essere utilizzato per ottenere il codice DnD per rilasciare il componente.

+0

Grazie per il tuo suggerimento Devon. No, temo che non cambi nulla. – tom

+0

Siamo spiacenti, ancora senza fortuna. Ho anche provato getDropTarget(). SetComponent (null), ma non funziona neanche. Il problema è che non c'è alcun codice all'interno di TransferHandler o delle sue classi interne per rimuovere DropHandler dall'AppContext. Semplicemente non c'è un remove() per completare il put() – tom

+0

Non sembra esserci alcun modo per rilasciare il riferimento detenuto da AppContext. Fortunatamente, la chiave utilizzata nel put è la classe di DropTarget, quindi può esserci solo 1 istanza in sospeso. Documentalo e minimizza il danno disponendo() rilascia tutto il resto. Come getContentPane(). RemoveAll(). –

0

Dopo aver setacciato la fonte delle classi pertinenti, sono convinto che questa sia una perdita inevitabile in TransferHandler.

L'oggetto nella mappa di AppContext, DropHandler, non viene mai rimosso. Poiché la chiave è l'oggetto DropHandler.class e DropHandler è una classe interna privata, l'unico modo per rimuoverlo dall'esterno di TransferHandler consiste nello svuotare l'intera mappa o con l'inganno della riflessione.

DropHandler contiene un riferimento a un oggetto TransferSupport, che non viene mai cancellato. L'oggetto TransferSupport contiene un riferimento al componente (nel mio caso un JFrame), che non viene eliminato.