Удалить контейнер верхнего уровня во время выполнения

К сожалению, похоже, что недавно закрывшийся question не был хорошо понят. Вот типичный вывод:

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)

Я постараюсь снова задать этот вопрос: как я могу запустить в Runtime первый открытый верхний уровень 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();
            }
        });
    }
}

Ответ 1

Вызов dispose() позволяет платформе хоста восстанавливать память, потребляемую супертяжелым сверстником, но она не может этого сделать до тех пор, пока Событие WINDOW_CLOSING обрабатывается на EventQueue. Даже тогда gc() является предложением.

Добавление: Еще один способ увидеть кошмар - через профайлер. Выполняя приведенный ниже пример с jvisualvm, можно видеть, что периодическая коллекция никогда не возвращается к исходному уровню. Я преувеличил вертикальную ось, начав с искусственно маленькой кучи. Дополнительные примеры показаны здесь. Когда память очень ограничена, я использовал два подхода:

  • Emergent: Loop из командной строки, каждый раз запуская новую виртуальную машину.

  • Срочно: полностью исключить тяжеловесный компонент, работать без головной уборки и составлять в BufferedImage только с использованием 2D-графики и легких компонентов.

enter image description here

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.WindowEvent;
import javax.swing.JDialog;

/** @see https://stackoverflow.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();
                }
            }
        });
    }
}

Ответ 2

Я полностью переработал ваш пример:

  • Я упростил то, что не было нужно (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.

Надеюсь, это даст ясный и полный ответ на вашу проблему.

Ответ 3

с намерением убрать все сомнения по поводу 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();
            }
        });
    }
}

Ответ 4

Я не уверен, если вы зададите вопрос о "сборке мусора" или о том, как идентифицировать видимые диалоги.

Вы не можете контролировать сбор мусора. Вызов метода gc() - это всего лишь предложение.

Если вы хотите игнорировать "удаленные" диалоги, вы можете использовать метод isDisplayable(), чтобы проверить его статус.

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

На моей машине я обнаружил, что если я

a) создать 5 диалогов
б) закрыть диалоговые окна c) создать 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();
            }
        });
    }
}

Ответ 5

В AppContext есть таймаут, прежде чем некоторые ресурсы будут выпущены окончательно. Это значение равно 5 секундам. Таким образом, если вы подождете еще пять секунд, контекст будет располагать (последнюю) ссылку на ваше диалоговое окно.

wins = null;
Thread.sleep(5000);