Удалить контейнер верхнего уровня во время выполнения
К сожалению, похоже, что этот недавно закрытый вопрос не был хорошо понят. Вот типичный вывод:
run:
Trying to Remove JDialog
Remove Cycle Done :-)
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 1
-----------------------------------------------------------
Trying to Remove JDialog
Remove Cycle Done :-)
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 2
-----------------------------------------------------------
Trying to Remove JDialog
Remove Cycle Done :-)
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 3
-----------------------------------------------------------
Trying to Remove JDialog
Remove Cycle Done :-)
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
*** End of Cycle Without Success, Exit App ***
BUILD SUCCESSFUL (total time: 13 seconds)
Я попытаюсь задать этот вопрос еще раз: Как я могу получить во время выполнения первый открытый верхний уровень Container
, а помогите с закрытием для меня одного из Swing NightMares?
import java.awt.*;
import java.awt.event.WindowEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class RemoveDialogOnRuntime extends JFrame {
private static final long serialVersionUID = 1L;
private int contID = 1;
private boolean runProcess;
private int top = 20;
private int left = 20;
private int maxLoop = 0;
public RemoveDialogOnRuntime() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 300));
setTitle("Remove Dialog On Runtime");
setLocation(150, 150);
pack();
setVisible(true);
Point loc = this.getLocation();
top += loc.x;
left += loc.y;
AddNewDialog();
}
private void AddNewDialog() {
DialogRemove firstDialog = new DialogRemove();
remWins();
}
private void remWins() {
runProcess = true;
Thread th = new Thread(new RemTask());
th.setDaemon(false);
th.setPriority(Thread.MIN_PRIORITY);
th.start();
}
private class RemTask implements Runnable {
@Override
public void run() {
while (runProcess) {
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JDialog) {
System.out.println(" Trying to Remove JDialog");
wins[i].setVisible(false);
wins[i].dispose();
WindowEvent windowClosing = new WindowEvent(wins[i], WindowEvent.WINDOW_CLOSING);
wins[i].dispatchEvent(windowClosing);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(windowClosing);
Runtime runtime = Runtime.getRuntime();
runtime.gc();
runtime.runFinalization();
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(RemoveDialogOnRuntime.class.getName()).log(Level.SEVERE, null, ex);
}
}
wins = null;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
System.out.println(" Remove Cycle Done :-)");
Runtime.getRuntime().runFinalization();
Runtime.getRuntime().gc();
runProcess = false;
}
});
}
pastRemWins();
}
}
private void pastRemWins() {
System.out.println(" Checking if still exists any of TopLayoutContainers");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JFrame) {
System.out.println("JFrame");
wins[i].setVisible(true);
} else if (wins[i] instanceof JDialog) {
System.out.println("JDialog");
wins[i].setVisible(true);
}
}
if (wins.length > 1) {
wins = null;
maxLoop++;
if (maxLoop <= 3) {
System.out.println(" Will Try Remove Dialog again, CycleNo. " + maxLoop);
System.out.println(" -----------------------------------------------------------");
remWins();
} else {
System.out.println(" -----------------------------------------------------------");
System.out.println("*** End of Cycle Without Success, Exit App ***");
closeMe();
}
}
}
private void closeMe() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
System.exit(0);
}
});
}
private class DialogRemove extends JDialog {
private static final long serialVersionUID = 1L;
DialogRemove(final Frame parent) {
super(parent, "SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
private DialogRemove() {
setTitle("SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RemoveDialogOnRuntime superConstructor = new RemoveDialogOnRuntime();
}
});
}
}
5 ответов
Вызов dispose()
позволяет платформе хоста восстанавливать память, занятую тяжеловесным узлом, но это не может быть сделано до тех пор, пока WINDOW_CLOSING
событие обрабатывается на EventQueue
, Даже тогда, gc()
это предложение.
Приложение: Еще один способ увидеть кошмар через профилировщик. Выполнение приведенного ниже примера с jvisualvm
Можно видеть, что периодическая коллекция никогда не возвращается к базовой линии. Я преувеличил вертикальную ось, начав с искусственно небольшой кучи. Дополнительные примеры приведены здесь. Когда память очень ограничена, я использовал два подхода:
Emergent: цикл из командной строки, каждый раз запуская новую виртуальную машину.
Срочно: полностью исключить тяжелый компонент, работающий без головы и сочиняющий в
BufferedImage
используя только 2D-графику и легкие компоненты.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.WindowEvent;
import javax.swing.JDialog;
/** @see https://stackru.com/questions/6309407 */
public class DialogClose extends JDialog {
public DialogClose(int i) {
this.setTitle("Dialog " + String.valueOf(i));
this.setPreferredSize(new Dimension(320, 200));
}
private void display() {
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
passSomeTime();
this.setVisible(false);
this.dispatchEvent(new WindowEvent(
this, WindowEvent.WINDOW_CLOSING));
this.dispose();
passSomeTime();
}
private void passSomeTime() {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace(System.err);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
int count = 0;
while (true) {
new DialogClose(count++).display();
}
}
});
}
}
Я полностью переработал ваш пример:
- Я упростил то, что было не нужно (
setLocation()
неиспользованный конструктор...) - Я удалил код, который вызывает событие WINDOW_CLOSING (бесполезно)
- Я удалил код, который сбрасывает все окна, чтобы они снова стали видимыми (что помешает GC на них)
- Я использовал
javax.swing.Timer
вместоThread
для утилизации диалога - Я использовал
Thread
для форсирования GC (не очень хорошая идея в EDT) - Я изменил окончательный критерий успеха, чтобы проверить, что
Window.getWindows()
равно 2 (не 1), потому что в Swing, если вы откроете диалоговое окно без родителя, то будет создан специальный невидимый фрейм, чтобы использовать его в качестве родительского (на самом деле для всех диалогов, не имеющих владельца), после создания этот фрейм не может быть удален.
Полученный фрагмент выглядит следующим образом:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class RemoveDialogOnRuntime extends JFrame {
private static final long serialVersionUID = 1L;
private boolean runProcess;
private int maxLoop = 0;
private Timer timer;
public RemoveDialogOnRuntime() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 300));
setTitle("Remove Dialog On Runtime");
setLocation(150, 150);
pack();
setVisible(true);
addNewDialog();
}
private void addNewDialog() {
DialogRemove firstDialog = new DialogRemove();
remWins();
}
private void remWins() {
runProcess = true;
timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (runProcess) {
for (Window win: Window.getWindows()) {
if (win instanceof JDialog) {
System.out.println(" Trying to Remove JDialog");
win.dispose();
}
}
System.out.println(" Remove Cycle Done :-)");
runProcess = false;
new Thread() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runtime.getRuntime().gc();
}
}.start();
} else {
pastRemWins();
runProcess = true;
}
}
});
timer.setRepeats(true);
timer.start();
}
private void pastRemWins() {
System.out.println(" Checking if still exists any of TopLayoutContainers");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JFrame) {
System.out.println("JFrame");
} else if (wins[i] instanceof JDialog) {
System.out.println("JDialog");
} else {
System.out.println(wins[i].getClass().getSimpleName());
}
}
// We must expect 2 windows here: this (RemoveDialogOnRuntime) and the parent of all parentless dialogs
if (wins.length > 2) {
wins = null;
maxLoop++;
if (maxLoop <= 3) {
System.out.println(" Will Try Remove Dialog again, CycleNo. " + maxLoop);
System.out.println(" -----------------------------------------------------------");
remWins();
} else {
System.out.println(" -----------------------------------------------------------");
System.out.println("*** End of Cycle Without Success, Exit App ***");
closeMe();
}
} else {
timer.stop();
}
}
private void closeMe() {
System.exit(0);
}
private class DialogRemove extends JDialog {
private static final long serialVersionUID = 1L;
private DialogRemove() {
setTitle("SecondDialog");
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RemoveDialogOnRuntime superConstructor = new RemoveDialogOnRuntime();
}
});
}
}
Важные выводы:
- Вы не можете удалить невидимый фрейм, созданный Swing как родительский для всех диалогов без владельца
- Вы должны принудительно удалить GC для удаленного диалога из
Window.getWindows()
(это мне кажется ошибкой, но я думаю, что причина в том, что Swing держитWeakReference
для всех окон, и этоWeakReference
не освобождается, пока GC не произошел.
Надеюсь, что это дает четкий и полный ответ на вашу проблему.
С намерением развеять все сомнения по поводу EDT и подтвердить trashgod Обновленное предложение, затем вывод на консоль
run:
7163 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
405 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 1
-----------------------------------------------------------
3274 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
403 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 2
-----------------------------------------------------------
3271 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
406 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 3
-----------------------------------------------------------
3275 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
403 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
-----------------------------------------------------------
*** End of Cycle Without Success, Exit App ***
BUILD SUCCESSFUL (total time: 26 seconds)
из кода
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class RemoveDialogOnRuntime extends JFrame {
private static final long serialVersionUID = 1L;
private int contID = 1;
private boolean runProcess;
private int top = 20;
private int left = 20;
private int maxLoop = 0;
private javax.swing.Timer timer = null;
public RemoveDialogOnRuntime() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 300));
setTitle("Remove Dialog On Runtime");
setLocation(150, 150);
pack();
setVisible(true);
Point loc = this.getLocation();
top += loc.x;
left += loc.y;
AddNewDialog();
}
private void AddNewDialog() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
DialogRemove firstDialog = new DialogRemove();
startAA();
}
});
}
private void startAA() {
timer = new javax.swing.Timer(5000, updateAA());
timer.setRepeats(false);
timer.start();
}
public Action updateAA() {
return new AbstractAction("text load action") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
if (SwingUtilities.isEventDispatchThread()) {
Runnable doRun = new Runnable() {
@Override
public void run() {
remWins();
}
};
SwingUtilities.invokeLater(doRun);
} else {
Runnable doRun = new Runnable() {
@Override
public void run() {
remWins();
}
};
SwingUtilities.invokeLater(doRun);
}
}
};
}
private void remWins() {
Runtime runtime = Runtime.getRuntime();
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long max = runtime.maxMemory();
long used = total - free;
System.out.println(Math.round(used / 1e3) + " KB used before GC");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JDialog) {
System.out.println(" Trying to Remove JDialog");
wins[i].setVisible(false);
wins[i].dispose();
WindowEvent windowClosing = new WindowEvent(wins[i], WindowEvent.WINDOW_CLOSING);
wins[i].dispatchEvent(windowClosing);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(windowClosing);
runtime = Runtime.getRuntime();
runtime.gc();
runtime.runFinalization();
}
}
wins = null;
System.out.println(" Remove Cycle Done :-)");
runtime.runFinalization();
runtime.gc();
runtime = Runtime.getRuntime();
total = runtime.totalMemory();
free = runtime.freeMemory();
max = runtime.maxMemory();
used = total - free;
System.out.println(Math.round(used / 1e3) + " KB used after GC");
startOO();
}
private void startOO() {
timer = new javax.swing.Timer(5000, updateOO());
timer.setRepeats(false);
timer.start();
}
public Action updateOO() {
return new AbstractAction("text load action") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
timer.stop();
if (SwingUtilities.isEventDispatchThread()) {
Runnable doRun = new Runnable() {//really contraproductive just dealayed
@Override
public void run() {
pastRemWins();
}
};
SwingUtilities.invokeLater(doRun);
} else {
Runnable doRun = new Runnable() {
@Override
public void run() {
pastRemWins();
}
};
SwingUtilities.invokeLater(doRun);
}
}
};
}
private void pastRemWins() {
System.out.println(" Checking if still exists any of TopLayoutContainers");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JFrame) {
System.out.println("JFrame");
wins[i].setVisible(true);
} else if (wins[i] instanceof JDialog) {
System.out.println("JDialog");
wins[i].setVisible(true);
}
}
if (wins.length > 1) {
wins = null;
maxLoop++;
if (maxLoop <= 3) {
System.out.println(" Will Try Remove Dialog again, CycleNo. " + maxLoop);
System.out.println(" -----------------------------------------------------------");
remWins();
} else {
System.out.println(" -----------------------------------------------------------");
System.out.println("*** End of Cycle Without Success, Exit App ***");
closeMe();
}
}
startAA();
}
private void closeMe() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
System.exit(0);
}
});
}
private class DialogRemove extends JDialog {
private static final long serialVersionUID = 1L;
DialogRemove(final Frame parent) {
super(parent, "SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
private DialogRemove() {
setTitle("SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RemoveDialogOnRuntime superConstructor = new RemoveDialogOnRuntime();
}
});
}
}
Я не уверен, что вы спрашиваете о "сборке мусора" или о том, как идентифицировать видимые диалоги.
Вы не можете контролировать, когда сборка мусора завершена. Вызов метода gc() - только предложение.
Если вы хотите игнорировать "удаленные" диалоги, вы можете использовать метод isDisplayable(), чтобы проверить его состояние.
С помощью следующей программы я получил интересные результаты. Первым изменением, которое я сделал, было добавление некоторых компонентов в диалог, чтобы больше ресурсов использовалось для каждого диалога, что увеличило бы вероятность того, что ресурсы будут собираться мусором.
На моей машине я обнаружил, что если я
а) создать 5 диалогов
б) закрыть диалоги
в) создать 5 диалогов
Тогда первые 5 кажутся мусором.
Однако, если я создаю 5, затем закрываю, затем создаю 1, затем закрываю, это не похоже на работу.
Суть в том, что вы не можете зависеть от того, когда будет выполняться сборка мусора, поэтому я предлагаю вам использовать метод isDisplayable(), чтобы определить, как выполнять обработку. Кнопка "Показать диалоги" использует этот метод как часть отображаемого вывода.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DialogSSCCE extends JPanel
{
public static int count;
public DialogSSCCE()
{
JButton display = new JButton("Display Dialogs");
display.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Display Dialogs");
for (Window window: Window.getWindows())
{
if (window instanceof JDialog)
{
JDialog dialog = (JDialog)window;
System.out.println("\t" + dialog.getTitle() + " " + dialog.isDisplayable());
}
}
}
});
add( display );
JButton open = new JButton("Create Dialog");
open.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Create Dialog");
JDialog dialog = new JDialog();
dialog.getContentPane().setLayout(null);
for (int i = 0; i < 200; i++)
{
dialog.add( new JTextField("some text") );
}
dialog.setTitle("Dialog " + count++);
dialog.setLocation(count * 25, count * 25);
dialog.setVisible(true);
System.out.println("\tCreated " + dialog.getTitle());
}
});
add( open );
JButton close = new JButton("Close Dialogs");
close.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Close Dialogs");
for (Window window: Window.getWindows())
{
if (window instanceof JDialog)
{
JDialog dialog = (JDialog)window;
System.out.println("\tClosing " + dialog.getTitle());
dialog.dispose();
}
}
Runtime.getRuntime().gc();
}
});
add( close );
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("DialogSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new DialogSSCCE() );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Время ожидания определено в AppContext
прежде чем некоторые ресурсы будут выпущены, наконец. Это установлено примерно на 5 секунд. Таким образом, если вы подождете еще пять секунд, контекст также будет располагать (последней) ссылкой на ваш диалог.
wins = null;
Thread.sleep(5000);