2016-09-11 4 views
1

У меня есть приложение, которому нужно открыть несколько JFrames (это просмотрщик журналов, а иногда вам нужно увидеть кучу журналов в отдельных окнах для сравнения).JFrame никогда не собирает мусор

Похоже, что JVM (обновление Java 8 на OS X) содержит значительную ссылку на JFrame, что мешает ему собирать мусор и в конечном итоге приводит к выбросу OutOfMemoryError.

Чтобы устранить проблему, выполните эту проблему с максимальным размером кучи 200 мегабайт. Каждый раз, когда открывается окно, он потребляет 50 мегабайт оперативной памяти. Откройте три окна (используя 150 мегабайт оперативной памяти). Затем закройте три окна (которые вызывают dispose), которые должны освободить память. Затем попробуйте открыть четвертое окно. Вызывается OutOfMemoryError, и четвертое окно не открывается.

Я видел другие ответы, в которых говорилось, что память будет автоматически выпущена, когда это необходимо, чтобы избежать иссякновения, но это, похоже, не происходит.

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: «Окно и его подкомпоненты можно отображаемые вновь восстанавливая собственные ресурсы с последующим вызовом для упаковки или шоу «. Это может быть причиной того, что ссылка все еще присутствует. – mm759

+0

Возможно дубликат: http://www.stackoverflow.com/questions/7376993/does-disposing-a-jframe-cause-memory-leakage. Вы согласны? – mm759

+0

Это почти такой же вопрос - я не нашел удовлетворительного ответа. Мне нужно несколько окон, и это похоже на ошибку, которую разработчик несет ответственность за очистку содержимого окна после вызова функции dispose(). Я надеялся, что образец кода может получить лучший ответ. –

ответ

5

here Как показано, существует неприводимые утечки из-за неисправимые распределения, связанных с типичным компонентом принимающей стороны сверстников. Остаток ~ 2 МБ в процессе создания и размещения ~ 10 окон. В вашем случае доминирующая утечка связана с сохраненными экземплярами bigMemoryChunk. Один из подходов состоит в том, чтобы сделать экземпляры unreachable в WindowListener.

this.addWindowListener(new WindowAdapter() { 

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

Почему нам нужно установить bigMemoryChunk = null?

JFrame не имеет никакого прямого способа узнать, что каждый экземпляр в вашей программе имеет соответствующий экземпляр bigMemoryChunk. Такой объект становится пригодным для сбора мусора, когда он является unrechable; bigMemoryChunk - единственная ссылка на объект массива в этом случае, поэтому установка его на null делает его сразу же подходящим для последующей сборки мусора.

Если JFrame это единственное, что держит ссылку на bigMemoryChunk ... то почему бы не JFrame и bigMemoryChunk ... как получить мусора за окном были захоронены?

Возможно, вы сбиваете с толку containment с номером inheritance and composition. JFrame не «содержит ссылку на bigMemoryChunkJFrameимеет переменную экземпляра с именем bigMemoryChunk, которая содержит ссылку на объект массива. Небольшой объем памяти, потерянный для однорангового узла, принадлежит и управляется хостом. Большой объем памяти в bigMemoryChunk - это ответственность вашей программы. Вложенный WindowListener позволяет связать управление объектом массива с закрытием фрейма.

В приведенном ниже профиле показана серия из четырех подкадров, открытых; затем каждый из них закрывается, а в профилировщике - сборка мусора forced.

profile

Как профилированный:

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

Это определенно решает проблему, но должно это необходимо? Должна ли JVM освобождать ссылку на JFrame после вызова функции dispose() и когда код приложения не содержит ссылок на него? –

+0

@JesseBarnum: Во многом он делает именно это; маленький остаток [цитируется] (http://stackoverflow.com/a/6310284/230513) - это несколько МБ в течение ~ 10^3 окон. Разумеется, вам необходимо самостоятельно управлять сохранением связанных с приложением данных; см. также [* В чем разница между мягкой ссылкой и слабой ссылкой в ​​Java? *] (http://stackoverflow.com/q/299659/230513) – trashgod

+0

Почему нам нужно установить bigMemoryChunk = null, это Исходящая ссылка от JFrame? пожалуйста, объясни. – hunter

Смежные вопросы