2010-05-11 12 views
7

Tengo un JFrame que acepta caídas de archivos de nivel superior. Sin embargo, después de que se produjo una caída, las referencias al marco se mantienen indefinidamente dentro de algunas clases internas de Swing. Creo que la eliminación del marco debería liberar todos sus recursos, entonces, ¿qué estoy haciendo mal?Pérdida de memoria con arrastrar y soltar

Ejemplo

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

reproducir, ejecutar el código y soltar algunos archivos en el marco. Cierra el marco para que se deseche.

Para verificar la fuga, tomo un volcado de pila usando JConsole y lo analizo con el Eclipse Memory Analysis tool. Muestra que sun.awt.AppContext mantiene una referencia al cuadro a través de su hashmap. Parece que TransferSupport tiene la culpa.

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

¿Qué estoy haciendo mal? ¿Debería pedirle al código de soporte de DnD que se limpie de alguna manera?

estoy ejecutando JDK 1.6 update 19.

+0

Estoy empezando a pensar que esto es un error de JVM. Las clases DnD relevantes no tienen código para borrar las referencias ofensivas, así que a menos que DropHandler se elimine del mapa de AppContext de alguna manera (realmente no entiendo esa clase), la fuga permanecerá. – tom

+0

Esta publicación del foro describe un problema similar [http://forums.java.net/jive/thread.jspa?messageID=276311]. No obtuvo respuestas. – tom

Respuesta

4

Aunque DropHandler no se elimina del mapa estático de AppContext, esa no es realmente la causa principal, sino simplemente una de las causas de la cadena. (El controlador de caída está destinado a ser un singleton, y no se borra hasta que la clase AppContext esté descargada, lo que en la práctica nunca es). El uso de DropHandler singleton es por diseño.

La causa real de la fuga es que el DropHandler configura una instancia de TransferSupport, que se reutiliza para cada operación de DnD, y durante una operación de DnD, le proporciona una referencia al componente involucrado en DnD. El problema es que no borra la referencia cuando DnD termina. Si DropHandler llamó al TransferSupport.setDNDVariables(null,null) cuando salió el DnD, el problema desaparecería. Esta es también la solución más lógica, ya que la referencia al componente solo es necesaria mientras DnD está en progreso. Otros enfoques, como borrar el mapa de AppContext, eluden el diseño en lugar de corregir un pequeño descuido.

Pero incluso si solucionamos esto, el marco aún no se recogerá.Desafortunadamente, parece haber otro problema: cuando comente todo el código relacionado con DnD, reduciéndolo a solo un JFrame simple, esto tampoco se recopiló. La referencia de retención estaba en javax.swing.BufferStrategyPaintManager. Hay un bug report para esto, aún no se ha corregido.

Por lo tanto, si reparamos el DnD, pegamos otro problema de retención con el repintado. Afortunadamente, todos estos errores se aferran a un solo cuadro (¡con suerte, el mismo!), Por lo que no es tan malo como podría ser. El marco está dispuesto, por lo que se liberan recursos nativos, y se puede eliminar todo el contenido, lo que permite que se libere, reduciendo la gravedad de la pérdida de memoria.

Así que, para finalmente responder a su pregunta, no está haciendo nada malo, ¡solo le está dando un poco de tiempo a algunos de los errores en el JDK!

ACTUALIZACIÓN: El error gestor de repintado tiene una solución rápida - la adición de

-Dswing.bufferPerWindow=false 

Para las opciones de inicio de la JVM evita el fallo. Con este error cancelado, tiene sentido publicar una solución para el error DnD:

Para solucionar el problema de DnD, puede agregar una llamada a este método al final de 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

Gracias por la información. Había visto a BufferStrategyPaintManager reteniendo algunas clases, pero me distrajo con las cosas de DnD. Acepto que el truco de reflexión funcionará, pero no pretendo usarlo – tom

+0

Claro, es tu elección si usas el parche o no, pero ahora al menos conoces el alcance del problema y puedes decidir cómo lidiar con él eso. Como tú. Probablemente no me molestaría en agregar la solución y, sabiendo que el problema se puede mitigar parcialmente, probablemente no haga nada o, como máximo, elimine el contenido de los marcos en el momento de la eliminación. Pero eso es en retrospectiva ahora que se conoce el alcance del problema. Tenía mucha curiosidad por saber por qué estaba ocurriendo la fuga y cuáles fueron las consecuencias, y es bueno saber que si de repente encontramos que se necesita una solución, esa está disponible. ¡Puedo dormir tranquilo esta noche! :-) – mdma

1

¿Cambia sus resultados si se agrega esto a su clase?

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

Actualización: ¡Tengo otra llamada al disponer. Creo que establecer el objetivo de caída a nulo debería liberar algunas referencias más. No veo nada más en el componente que pueda usarse para obtener el código DnD para soltar su componente.

+0

Gracias por su sugerencia Devon. No, me temo que no cambia nada. – tom

+0

Lo sentimos, todavía no hay suerte. También probé getDropTarget(). SetComponent (null), pero tampoco funciona. El problema es que no hay ningún código dentro de TransferHandler o sus clases internas para eliminar el DropHandler de AppContext. Simplemente no hay un remove() para complementar el put() – tom

+0

No parece haber ninguna forma de liberar la referencia mantenida por AppContext. Afortunadamente, la clave utilizada en el put es la Clase de DropTarget, por lo que solo puede haber 1 instancia pendiente. Documentarlo y minimizar el daño al disponer() liberar todo lo demás. Al igual que getContentPane(). RemoveAll(). –

0

Después de haber recorrido la fuente de las clases relevantes, estoy convencido de que esta es una filtración inevitable en TransferHandler.

El objeto en el mapa de AppContext, un DropHandler, nunca se elimina. Dado que la clave es el objeto DropHandler.class, y DropHandler es una clase interna privada, la única forma en que se puede eliminar desde fuera de TransferHandler es vaciando todo el mapa o con el engaño de reflexión.

DropHandler contiene una referencia a un objeto TransferSupport, que nunca se borra. El objeto TransferSupport contiene una referencia al componente (en mi caso, un JFrame), que tampoco se borra.