2010-05-11 13 views
7

Ich habe einen JFrame, der Top-Level-Tropfen von Dateien akzeptiert. Nach einem Absturz werden Verweise auf den Rahmen jedoch innerhalb bestimmter interner Swing-Klassen unbegrenzt beibehalten. Ich glaube, dass die Freigabe des Rahmens alle Ressourcen freisetzen sollte. Was mache ich also falsch?Speicherleck mit Swing Drag & Drop

Beispiel

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

Um den Code und legen Sie einige Dateien auf dem Rahmen zu reproduzieren, laufen. Schließen Sie den Rahmen, damit er entsorgt wird.

Um das Leck zu überprüfen, nehme ich einen Heap-Dump mit JConsole und analysiere es mit der Eclipse Memory Analysis tool. Es zeigt, dass sun.awt.AppContext einen Verweis auf den Frame durch seine Hashmap hält. Es sieht so aus, als wäre TransferSupport schuld.

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

Was mache ich falsch? Sollte ich den DnD-Support-Code bitten, sich irgendwie aufzuräumen?

Ich bin mit JDK 1.6 Update 19

+0

Ich fange an zu denken, dass dies ein JVM-Bug ist. Die relevanten DnD-Klassen haben keinen Code, um die fehlerhaften Verweise zu löschen. Wenn also der DropHandler nicht irgendwie aus der AppContext-Map entfernt wird (ich verstehe diese Klasse nicht wirklich), bleibt das Leck bestehen. – tom

+0

Dieser Forenbeitrag beschreibt ein ähnliches Problem [http://forums.java.net/jive/thread.jspa?messageID=276311]. Es hat keine Antworten erhalten. – tom

Antwort

4

Obwohl der DropHandler nicht aus der statischen AppContext-Map entfernt wird, ist dies nicht die eigentliche Ursache, sondern nur eine der Ursachen in der Kette. (Der Drop-Handler soll ein Singleton sein und wird nicht gelöscht, bis die AppContext-Klasse entladen ist, was in der Praxis niemals der Fall ist.) Die Verwendung eines Singleton-DropHandlers ist beabsichtigt.

Die wahre Ursache des Lecks ist, dass der DropHandler eine Instanz von TransferSupport einrichtet, die für jede DnD-Operation wiederverwendet wird und während einer DnD-Operation einen Verweis auf die in DnD involvierte Komponente bereitstellt. Das Problem ist, dass es die Referenz nicht löscht, wenn DnD beendet. Wenn der DropHandler TransferSupport.setDNDVariables(null,null) aufgerufen hat, als das DnD beendet wurde, würde das Problem verschwinden. Dies ist auch die logischste Lösung, da der Verweis auf die Komponente nur während der Ausführung von DnD erforderlich ist. Andere Ansätze, z. B. das Löschen der AppContext-Map, umgehen das Design, anstatt eine kleine Übersicht zu erstellen.

Aber selbst wenn wir das beheben, würde der Rahmen immer noch nicht gesammelt werden.Unglücklicherweise scheint es ein anderes Problem zu geben: Als ich den gesamten DnD-Code auskommentiert habe, der auf einen einfachen JFrame reduziert wurde, wurde auch dieser nicht gesammelt. Die Retard-Referenz war in javax.swing.BufferStrategyPaintManager. Dafür gibt es eine bug report, die noch nicht fixiert ist.

Also, wenn wir die DnD beheben, treffen wir ein anderes Aufbewahrungsproblem mit Neuanstrich. Glücklicherweise halten sich all diese Fehler an nur einem Frame (hoffentlich dem gleichen!), Also ist es nicht so schlimm wie es sein könnte. Der Rahmen ist angeordnet, so dass native Ressourcen freigegeben werden und der gesamte Inhalt entfernt werden kann. Dadurch wird die Freigabe ermöglicht und die Schwere des Speicherverlusts verringert.

Also, um Ihre Frage endlich zu beantworten, tun Sie nichts falsch, Sie geben nur einige der Fehler in der JDK ein wenig Sendezeit!

UPDATE: Der Repaint Manager Bug hat eine schnelle Lösung -

-Dswing.bufferPerWindow=false 

der JVM-Startoptionen vermeidet den Fehler hinzufügen. Wenn dieser Fehler behoben ist, ist es sinnvoll, eine Fehlerbehebung für den DnD-Fehler zu veröffentlichen:

Um das DnD-Problem zu beheben, können Sie einen Aufruf dieser Methode am Ende von ImportData() hinzufügen.

  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

Danke für die Info. Ich hatte bemerkt, dass BufferStrategyPaintManager einige Klassen behalten hatte, war aber von den DnD-Dingen abgelenkt. Ich akzeptiere, dass der Reflektionstrick funktioniert - aber ich beabsichtige nicht, ihn zu verwenden – tom

+0

Sicher, es ist deine Entscheidung, ob du den Fix verwendest oder nicht, aber jetzt weißt du wenigstens den Umfang des Problems und kannst entscheiden, wie du damit umgehen willst es. Wie du. Ich würde wahrscheinlich nicht die Korrektur hinzufügen, und in dem Wissen, dass das Problem teilweise gemildert werden kann, wahrscheinlich tun nichts oder höchstens, entfernen Sie Inhalte aus Frames auf Entsorgen. Aber das ist im Nachhinein das Ausmaß des Problems bekannt. Ich war sehr neugierig zu erfahren, warum das Leck passierte und was die Konsequenzen waren, und es ist schön zu wissen, dass, wenn wir plötzlich eine Lösung finden, diese verfügbar ist. Ich kann heute Nacht ruhig schlafen! :-) – mdma

1

Hat es Ihre Ergebnisse ändern, wenn Sie diese zu Ihrer Klasse hinzufügen?

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

Update: Ich habe einen anderen Aufruf der dispose hinzugefügt. Ich denke, das Setzen des Drop-Ziels auf null sollte einige weitere Referenzen freigeben. Ich sehe nichts anderes auf der Komponente, die verwendet werden kann, um den DnD-Code zum Loslassen Ihrer Komponente zu bekommen.

+0

Danke für Ihren Vorschlag Devon. Nein, ich fürchte, es ändert nichts. – tom

+0

Sorry, immer noch kein Glück. Ich habe auch versucht getDropTarget(). SetComponent (null), aber es funktioniert auch nicht. Das Problem besteht darin, dass in TransferHandler oder seinen inneren Klassen kein Code vorhanden ist, um den DropHandler aus dem AppContext zu entfernen. Es gibt nur keine remove(), um die put() – tom

+0

zu ergänzen. Es scheint keine Möglichkeit zu geben, die von AppContext gehaltene Referenz freizugeben. Glücklicherweise ist der Schlüssel, der in dem Put verwendet wird, die Klasse von DropTarget, also kann es nur 1 ausstehende Instanz geben. Dokumentieren Sie es und minimieren Sie den Schaden, indem Sie dispose() alles andere freigeben. Wie getContentPane(). RemoveAll(). –

0

Nachdem ich die Quelle der relevanten Klassen durchforstet habe, bin ich überzeugt, dass dies ein unvermeidliches Leck im TransferHandler ist.

Das Objekt in der Map von AppContext, ein DropHandler, wird nie entfernt. Da der Schlüssel das DropHandler.class -Objekt ist und DropHandler eine private innere Klasse ist, kann die einzige Möglichkeit, sie von außerhalb von TransferHandler zu entfernen, darin bestehen, die gesamte Map zu leeren oder mit Reflektions-Tricks.

DropHandler enthält einen Verweis auf ein TransferSupport-Objekt, das niemals gelöscht wird. Das TransferSupport-Objekt enthält einen Verweis auf die Komponente (in meinem Fall ein JFrame), der ebenfalls nicht gelöscht wird.