2016-09-11 1 views
1

J'ai une application qui doit ouvrir plusieurs JFrames (c'est une visionneuse de journal, et parfois vous avez besoin de voir un tas de journaux dans des fenêtres séparées pour comparer). Il semble que la JVM (Java 8 mise à jour 101 sous OS X) contient une référence forte au JFrame, ce qui l'empêche d'être récupérée et génère éventuellement un OutOfMemoryError.JFrame n'est jamais ramassé

Pour voir le problème, exécutez ce problème avec une taille de tas maximale de 200 mégaoctets. Chaque fois qu'une fenêtre est ouverte, elle consomme 50 mégaoctets de RAM. Ouvrez trois fenêtres (en utilisant 150 mégaoctets de RAM). Fermez ensuite les trois fenêtres (lesquelles appellent disposent), ce qui devrait libérer de la mémoire. Ensuite, essayez d'ouvrir une quatrième fenêtre. Une OutOfMemoryError est levée et la quatrième fenêtre ne s'ouvre pas.

J'ai vu d'autres réponses indiquant que la mémoire sera automatiquement libérée si nécessaire pour éviter de manquer, mais cela ne semble pas se produire.

package com.prosc.swing; 

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.text.NumberFormat; 

public class WindowLeakTest { 
    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      public void run() { 
       JFrame launcherWindow = new JFrame("Launcher window"); 
       JButton launcherButton = new JButton("Open new JFrame"); 
       launcherButton.addActionListener(new ActionListener() { 
        public void actionPerformed(ActionEvent e) { 
         JFrame subFrame = new JFrame("Sub frame") { 
          private byte[] bigMemoryChunk = new byte[ 50 * 1024 * 1024 ]; //50 megabytes of memory 

          protected void finalize() throws Throwable { 
           System.out.println("Finalizing window (Never called until after OutOfMemory is thrown)"); 
           super.finalize(); 
          } 
         }; 
         subFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
         subFrame.add(new JLabel("Nothing to see here")); 
         subFrame.pack(); 
         subFrame.setVisible(true); 
         System.out.println("Memory usage after new window: " + getMemoryInfo()); 
        } 
       }); 
       launcherWindow.add(launcherButton); 
       launcherWindow.pack(); 
       launcherWindow.setVisible(true); 

       new Timer(5000, new ActionListener() { 
        public void actionPerformed(ActionEvent e) { 
         System.gc(); 
         System.out.println("Current memory usage after garbage collection: " + getMemoryInfo()); 
        } 
       }).start(); 
      } 
     }); 
    } 

    public static String getMemoryInfo() { 
     NumberFormat numberFormat = NumberFormat.getNumberInstance(); 
     return "Max heap size is " + numberFormat.format(Runtime.getRuntime().maxMemory()) + "; free memory is " + numberFormat.format(Runtime.getRuntime().freeMemory()) + "; total memory is " + numberFormat.format(Runtime.getRuntime().totalMemory()); 
    } 
} 
+0

Javadoc Java SE 7 Window.dispose: « La fenêtre et ses sous-composants peut être fait affichable à nouveau par la reconstruction des ressources indigènes avec un appel ultérieur à emballer ou afficher " Cela pourrait être la raison pour laquelle une référence est toujours présente. – mm759

+0

Peut-être dupliquer: http://www.stackoverflow.com/questions/7376993/does-disposing-a-jframe-cause-memory-leakage. Êtes-vous d'accord? – mm759

+0

Ils sont à peu près la même question - je n'ai pas vu une réponse satisfaisante si. J'ai besoin de plusieurs fenêtres, et il semble que le développeur soit responsable de l'effacement du contenu d'une fenêtre après l'appel de dispose(). J'espérais qu'un échantillon de code pourrait susciter une meilleure réponse. –

Répondre

5

Comme le montre here, il y a une fuite irréductible due aux allocations irrécupérables liées à un composant homologue hôte typique. Le reste est d'environ 2 Mo au cours de la création et de l'élimination de ~ 10 fenêtres . Dans votre cas, la fuite dominante est due aux instances retenues de bigMemoryChunk. Une approche consiste à créer les instances unreachable dans un WindowListener.

this.addWindowListener(new WindowAdapter() { 

    @Override 
    public void windowClosing(WindowEvent e) { 
     bigMemoryChunk = null; 
    } 
}); 

Pourquoi avons-nous besoin de mettre bigMemoryChunk = null?

JFrame n'a aucun moyen direct de savoir que chaque fois que votre programme a une instance associée bigMemoryChunk. Un tel objet devient éligible pour le ramassage des ordures lorsqu'il est unrechable; bigMemoryChunk est la seule référence à l'objet tableau dans ce cas, donc en le définissant à null le rend immédiatement éligible pour la récupération de place ultérieure.

Si le JFrame est la seule chose qui représente une référence à bigMemoryChunk ... alors pourquoi ne pas le JFrame et bigMemoryChunk ... à la fois obtenir les déchets ramassés après la fenêtre a été placée?

Vous pouvez être déroutant containment avec inheritance and composition. Le JFrame ne "détient pas une référence à bigMemoryChunk;" le JFramea une variable d'instance nommée bigMemoryChunk qui contient une référence à un objet tableau. La petite quantité de mémoire perdue à l'homologue du cadre est détenue et gérée par l'hôte. La grande quantité de mémoire dans bigMemoryChunk est la responsabilité de votre programme. Le WindowListener joint vous permet d'associer la gestion de l'objet tableau à la fermeture du cadre.

Le profil ci-dessous montre une série de quatre sous-trames ouvertes; chacun est ensuite fermé, suivi d'une collecte des ordures forced dans le profileur.

profile

Comme profilé:

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 
import java.text.NumberFormat; 

public class WindowLeakTest { 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       JFrame launcherWindow = new JFrame("Launcher window"); 
       launcherWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 
       JButton launcherButton = new JButton("Open new JFrame"); 
       launcherButton.addActionListener(new ActionListener() { 
        @Override 
        public void actionPerformed(ActionEvent e) { 
         JFrame subFrame = new JFrame("Sub frame") { 
          private byte[] bigMemoryChunk = new byte[50 * 1024 * 1024]; 

          { 
           this.addWindowListener(new WindowAdapter() { 

            @Override 
            public void windowClosing(WindowEvent e) { 
             bigMemoryChunk = null; 
            } 
           }); 
          } 

          @Override 
          protected void finalize() throws Throwable { 
           super.finalize(); 
           System.out.println("Finalizing window."); 
          } 
         }; 
         subFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
         subFrame.add(new JLabel("Nothing to see here")); 
         subFrame.pack(); 
         subFrame.setVisible(true); 
        } 
       }); 
       launcherWindow.add(launcherButton); 
       launcherWindow.pack(); 
       launcherWindow.setVisible(true); 
      } 
     }); 
    } 
} 
+0

Cela résout définitivement le problème, mais devrait ce sera nécessaire? La JVM ne devrait-elle pas publier la référence au JFrame après avoir appelé dispose() et quand le code de l'application ne contient aucune référence? –

+0

@JesseBarnum: En grande partie, c'est juste cela; le petit reste [cité] (http://stackoverflow.com/a/6310284/230513) est un peu de Mo au cours de ~ 10^3 fenêtres. Bien sûr, vous devez gérer vous-même la rétention des données associées à l'application. voir aussi [* Quelle est la différence entre une référence souple et une référence faible en Java? *] (http://stackoverflow.com/q/299659/230513) – trashgod

+0

Pourquoi avons-nous besoin de définir bigMemoryChunk = null, c'est un référence sortante de JFrame? S'il vous plaît, expliquez. – hunter