2010-05-11 15 views
7

Mam JFrame, która akceptuje najwyższego poziomu krople plików. Jednak po wystąpieniu kropli odniesienia do ramki są przechowywane bez końca w niektórych klasach wewnętrznych Swinga. Uważam, że pozbycie się ramy powinno uwolnić wszystkie jej zasoby, więc co robię źle?Wyciek pamięci z Swing Przeciągnij i upuść

Przykład

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

Aby odtworzyć, uruchomić kod i upuścić kilka plików na ramie. Zamknij ramkę, aby się pozbyć.

Aby zweryfikować wyciek, wykonuję zrzut sterty za pomocą programu JConsole i analizuję go za pomocą Eclipse Memory Analysis tool. Pokazuje, że sun.awt.AppContext zawiera odwołanie do ramki poprzez swoją mapę. Wygląda na to, że TransferSupport jest wadliwy.

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

Co robię źle? Czy powinienem poprosić o kod obsługi DnD, aby jakoś się oczyścił?

biegnę JDK 1.6 aktualizacji 19.

+0

Zaczynam myśleć, że to błąd JVM. Odpowiednie klasy DnD nie mają kodu, aby usunąć niepożądane odwołania, więc jeśli dropHandler nie zostanie w jakiś sposób usunięty z mapy AppContext (tak naprawdę nie rozumiem tej klasy), wyciek pozostanie. – tom

+0

Ten wpis na forum opisuje podobny problem [http://forums.java.net/jive/thread.jspa?messageID=276311]. Nie otrzymał odpowiedzi. – tom

Odpowiedz

4

Mimo że obiekt DropHandler nie został usunięty ze statycznej mapy AppContext, nie jest to tak naprawdę podstawowa przyczyna, ale tylko jedna z przyczyn w łańcuchu. (Procedura obsługi upuszczania ma być pojedynczą i nie jest usuwana, dopóki klasa AppContext nie zostanie załadowana, co w praktyce nigdy nie jest możliwe.) Używanie singletonu DropHandler jest zgodne z projektem.

Prawdziwą przyczyną wycieku jest to, że DropHandler tworzy instancję TransferSupport, która jest ponownie używana dla każdej operacji DnD, a podczas operacji DnD udostępnia odniesienie do komponentu zaangażowanego w DnD. Problem polega na tym, że nie usuwa odniesienia, gdy DnD kończy się. Jeśli DropHandler zadzwonił pod numer TransferSupport.setDNDVariables(null,null) po wyjściu DnD, problem znikał. Jest to również najbardziej logiczne rozwiązanie, ponieważ odniesienie do komponentu jest wymagane tylko podczas trwania DnD. Inne podejścia, takie jak czyszczenie mapy AppContext, omijają projekt zamiast naprawiania małego niedopatrzenia.

Ale nawet jeśli to naprawimy, ramka nadal nie zostanie zebrana.Niestety pojawia się inny problem: Kiedy skomentowałem cały kod związany z DnD, redukując tylko do prostego JFrame, to też nie było zbierane. Odniesienie do retaningu było w javax.swing.BufferStrategyPaintManager. Istnieje bug report dla tego, jak dotąd nieoprawiony.

Tak więc, jeśli naprawimy DnD, trafiamy na inny problem z retencją przy malowaniu. Na szczęście wszystkie te błędy trzymają tylko jedną ramkę (miejmy nadzieję, tę samą!), Więc nie jest tak źle, jak mogłoby być. Ramka jest ułożona, więc rodzime zasoby są zwalniane, a cała zawartość może zostać usunięta, co pozwala na uwolnienie, zmniejszając nasilenie wycieku pamięci.

Tak więc, aby w końcu odpowiedzieć na twoje pytanie, nie robisz nic złego, po prostu dajesz trochę błędów w JDK trochę czasu antenowego!

UPDATE: Menedżer repaint bug ma szybkiego rozwiązania - dodanie

-Dswing.bufferPerWindow=false 

do opcji uruchamiania JVM pozwala uniknąć błędów. Po usunięciu tego błędu warto opublikować poprawkę dotyczącą błędu DnD:

Aby naprawić problem DnD, możesz dodać wywołanie tej metody na końcu metody 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

Dzięki za informacje. Zauważyłem, że BufferStrategyPaintManager zachowuje niektóre klasy, ale był rozproszony w kwestii DnD. Zgadzam się, że sztuczka z odbiciem zadziała - ale nie zamierzam jej używać. – tom

+0

Oczywiście, to twój wybór, jeśli używasz poprawki, ale teraz przynajmniej znasz zakres problemu i możesz zdecydować, jak sobie z tym poradzić. to. Jak ty. Prawdopodobnie nie zawracałbym sobie głowy dodawaniem poprawki, a wiedząc, że problem może zostać częściowo złagodzony, prawdopodobnie nie robię nic lub co najwyżej usuwam zawartość z klatek na sprzedaż. Ale to już jest z perspektywy czasu, teraz znany jest zakres problemu. Byłem bardzo ciekawy, dlaczego nastąpił wyciek i jakie były tego konsekwencje, i miło jest wiedzieć, że jeśli nagle stwierdzimy, że jest potrzebna poprawka, ta jest dostępna. Mogę spać spokojnie dziś wieczorem! :-) – mdma

1

Czy zmienić swoje wyniki, jeśli dodać do swojej klasie?

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

Aktualizacja: Dodałem kolejne wezwanie do utylizować. Myślę, że ustawienie wartości docelowej drop na wartość null powinno zwolnić więcej odwołań. Nie widzę niczego więcej na komponencie, który może być użyty do uzyskania kodu DnD, aby zwolnić twój komponent.

+0

Dzięki za sugestię Devon. Nie, obawiam się, że nic nie zmienia. – tom

+0

Przepraszam, wciąż nie ma szczęścia. Próbowałem również getDropTarget(). SetComponent (null), ale to też nie działa. Problem polega na tym, że nie ma żadnego kodu wewnątrz TransferHandler lub jego wewnętrznych klas, aby usunąć DropHandler z AppContext. Po prostu nie ma żadnego usunięcia(), które byłoby uzupełnieniem put() – tom

+0

. Nie wydaje się, że istnieje jakikolwiek sposób na uwolnienie odwołania utrzymywanego przez AppContext. Na szczęście kluczem użytym w lay jest klasa DropTarget, więc może istnieć tylko jedna wyjątkowa instancja. Poinformuj go i zminimalizuj obrażenia, usuwając wszystko(). Podobnie jak getContentPane(). RemoveAll(). –

0

Po przetarciu źródła odpowiednich klas, jestem przekonany, że jest to nieunikniony przeciek w TransferHandler.

Obiekt w mapie AppContext, DropHandler, nigdy nie jest usuwany. Ponieważ kluczem jest obiekt DropHandler.class, a DropHandler jest prywatną klasą wewnętrzną, jedynym sposobem, w jaki można go usunąć z zewnątrz TransferHandler, jest opróżnienie całej mapy lub oszustwo z odbicia.

DropHandler zawiera odniesienie do obiektu TransferSupport, który nigdy nie jest czyszczony. Obiekt TransferSupport zawiera odwołanie do komponentu (w moim przypadku JFrame), który również nie jest czyszczony.