Утечка памяти с помощью Swing Drag and Drop - PullRequest
7 голосов
/ 11 мая 2010

У меня есть JFrame, который принимает файлы верхнего уровня. Однако после того, как произошел сброс, ссылки на фрейм хранятся неопределенно внутри некоторых внутренних классов Swing. Я считаю, что утилизация фрейма должна освободить все его ресурсы, так что я делаю не так?

Пример

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

Чтобы воспроизвести, запустите код и поместите некоторые файлы в рамку. Закройте рамку, чтобы она была утилизирована.

Чтобы проверить утечку, я беру дамп кучи с помощью JConsole и анализирую его с помощью инструмента анализа памяти Eclipse . Это показывает, что sun.awt.AppContext содержит ссылку на фрейм через свою хэш-карту. Похоже, что TransferSupport виноват.

образ пути к корню GC http://img402.imageshack.us/img402/4444/dndleak.png

Что я делаю не так? Должен ли я попросить код поддержки DnD как-то себя очистить?

Я использую JDK 1.6 с обновлением 19.

Ответы [ 3 ]

4 голосов
/ 18 мая 2010

Хотя DropHandler не удаляется из статической карты AppContext, на самом деле это не основная причина, а лишь одна из причин в цепочке. (Обработчик отбрасывания должен быть одноэлементным и не очищаться до тех пор, пока класс AppContext не будет выгружен, чего на практике никогда не бывает.) Использование одноэлементного DropHandler разработано специально.

Настоящая причина утечки заключается в том, что DropHandler устанавливает экземпляр TransferSupport, который повторно используется для каждой операции DnD, а во время операции DnD предоставляет ему ссылку на компонент, участвующий в DnD. Проблема в том, что не очищает ссылку, когда DnD заканчивает . Если DropHandler вызовет TransferSupport.setDNDVariables(null,null), когда DnD выйдет, то проблема исчезнет. Это также наиболее логичное решение, поскольку ссылка на компонент требуется только во время выполнения DnD. Другие подходы, такие как очистка карты AppContext, обходят дизайн, а не исправляют небольшой недосмотр.

Но даже если мы исправим это, кадр все равно не будет собран. К сожалению, кажется, есть другая проблема: когда я закомментировал весь код, связанный с DnD, сводя его к простому JFrame, он тоже не собирался. Ссылка на возврат был в javax.swing.BufferStrategyPaintManager. Для этого есть отчет об ошибке , который еще не исправлен.

Итак, если мы исправим DnD, мы столкнемся с другой проблемой хранения с перерисовкой. К счастью, все эти ошибки держатся только на одном кадре (надеюсь, на одном и том же!), Так что это не так плохо, как могло бы быть. Фрейм располагается таким образом, что исходные ресурсы освобождаются, и весь контент может быть удален, что позволяет его освободить, уменьшая серьезность утечки памяти.

Итак, чтобы окончательно ответить на ваш вопрос, вы не делаете ничего плохого, вы просто даете некоторым ошибкам в JDK немного эфирного времени!

ОБНОВЛЕНИЕ: Исправлена ​​ошибка в менеджере перерисовок - добавление

-Dswing.bufferPerWindow=false

В опциях запуска jvm ошибка отсутствует. После устранения этой ошибки имеет смысл опубликовать исправление ошибки DnD:

Чтобы исправить проблему DnD, вы можете добавить вызов этого метода в конце 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)
                {
                }
            }
1 голос
/ 11 мая 2010

Изменится ли ваш результат, если вы добавите это в свой класс?

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

Обновление: Я добавил еще один вызов в распоряжение. Я думаю, что установка цели удаления на ноль должна освободить еще несколько ссылок. Я не вижу в компоненте ничего другого, что можно было бы использовать для получения кода DnD, позволяющего освободить ваш компонент.

0 голосов
/ 13 мая 2010

Изучив источник соответствующих классов, я убежден, что это неизбежная утечка в TransferHandler.

Объект на карте AppContext, DropHandler, никогда не удаляется. Поскольку ключом является объект DropHandler.class, а DropHandler является закрытым внутренним классом, единственный способ удалить его из-за пределов TransferHandler - это очистить всю карту или использовать хитрость отражения.

DropHandler содержит ссылку на объект TransferSupport, который никогда не очищается. Объект TransferSupport содержит ссылку на компонент (в моем случае это JFrame), который также не очищается.

...