Есть ли безопасный способ использовать очиститель для отмены регистрации прослушивателя?

0

Вопрос

У меня есть класс действий Swing, который работает следующим образом:

package org.trypticon.hex.gui.datatransfer;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import javax.annotation.Nonnull;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.TransferHandler;

import org.trypticon.hex.gui.Resources;
import org.trypticon.hex.gui.util.FinalizeGuardian;
import org.trypticon.hex.gui.util.FocusedComponentAction;

public class PasteAction extends FocusedComponentAction {
    private final FlavorListener listener = (event) -> {
        // this method in the superclass calls back `shouldBeEnabled`
        updateEnabled();
    };

    @SuppressWarnings({"UnusedDeclaration"})
    private final Object finalizeGuardian = new FinalizeGuardian(() -> {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.removeFlavorListener(listener);
    });

    public PasteAction() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.addFlavorListener(listener);
    }

    @Override
    protected boolean shouldBeEnabled(@Nonnull JComponent focusOwner) {
        TransferHandler transferHandler = focusOwner.getTransferHandler();
        if (transferHandler == null) {
            return false;
        }

        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        DataFlavor[] flavorsInClipboard = clipboard.getAvailableDataFlavors();
        return transferHandler.canImport(focusOwner, flavorsInClipboard);
    }

    @Override
    protected void doAction(@Nonnull JComponent focusOwner) throws Exception {
        Action action = TransferHandler.getPasteAction();
        action.actionPerformed(new ActionEvent(
            focusOwner, ActionEvent.ACTION_PERFORMED, (String) action.getValue(Action.NAME)));
    }
}

То FinalizeGuardian упомянутый здесь в настоящее время реализован с использованием finalize():

package org.trypticon.hex.gui.util;

public final class FinalizeGuardian {
    private final Runnable cleanupLogic;

    public FinalizeGuardian(Runnable cleanupLogic) {
        this.cleanupLogic = cleanupLogic;
    }

    @Override
    protected final void finalize() throws Throwable {
        try {
            cleanupLogic.run();
        } finally {
            super.finalize();
        }
    }
}

Итак, по очевидным причинам я хотел бы переключиться на использование Cleaner для этого.

Первая попытка была примерно такой:

package org.trypticon.hex.gui.util;

import java.lang.ref.Cleaner;

public final class FinalizeGuardian {
    private static final Cleaner cleaner = Cleaner.create();

    public FinalizeGuardian(Runnable cleanupLogic) {
        cleaner.register(this, cleanupLogic);
    }
}

Проблема в том, что теперь объект никогда не станет доступным для фантома, потому что:

  • Cleaner сама по себе содержит сильную ссылку на cleanupLogic
  • cleanupLogic содержит ссылку на listener для того, чтобы удалить слушателя
  • listener содержит ссылку на класс действий для вызова updateEnabled на нем
  • класс действий содержит ссылку на FinalizeGuardian чтобы его не забрали преждевременно

Потому что FinalizeGuardian само по себе никогда не станет достижимым фантомом, уборщика никогда не вызовут.

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

garbage-collection java swing
2021-11-24 01:39:09
1

Лучший ответ

3

До тех пор, пока FlavorListener зарегистрирован в источнике событий, он никогда не станет недостижимым (пока источник событий все еще доступен). Это означает, что PasteAction экземпляр, который прослушиватель обновляет, также никогда не станет недоступным, так как у прослушивателя есть сильная ссылка на него.

Единственный способ отделить их достижимость-это изменить прослушиватель, чтобы поддерживать только слабую ссылку на объект, который он обновляет. Обратите внимание, что когда вы используете Cleaner вместо finalize(), в FinalizeGuardian устарел.

Код будет выглядеть так

public class PasteAction extends FocusedComponentAction {

    static FlavorListener createListener(WeakReference<PasteAction> r) {
        return event -> {
            PasteAction pa = r.get();
            if(pa != null) pa.updateEnabled();
        };
    }

    private static final Cleaner CLEANER = Cleaner.create();

    static void prepareCleanup(
                       Object referent, Clipboard clipboard, FlavorListener listener) {

        CLEANER.register(referent, () -> clipboard.removeFlavorListener(listener));
    }

    public PasteAction() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        FlavorListener listener = createListener(new WeakReference<>(this));
        clipboard.addFlavorListener(listener);
        prepareCleanup(this, clipboard, listener);
    }

…

Обратите внимание, что критические части были помещены в static методы, позволяющие случайно захватить this ссылка невозможна. Эти методы получают минимум, необходимый для выполнения своей работы, createListener получает только слабую ссылку на действие и prepareCleanup возвращает референт как Object, поскольку действие очистки не должно обращаться к каким-либо элементам действия, но получать необходимые значения в виде отдельных параметров.

Но после того, как я показал, как может выглядеть использование очистителя, я должен настоятельно не рекомендовать использовать этот механизм, особенно в качестве единственного механизма очистки. Здесь это влияет не только на потребление памяти, но и на поведение программы, потому что до тех пор, пока ссылки не будут удалены, слушатель будет продолжать получать информацию и обновлять устаревший объект.

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

Что еще хуже, при одновременных сборщиках мусора возможно даже, что их цикл сбора неоднократно перекрывается с фактически устаревшим выполнением updateEnabled() инициируется слушателем (потому что ссылка еще не была очищена). Что будет активно препятствовать сбору мусора из этих объектов, даже если сборщик мусора запускается и в противном случае собирал бы их.

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

2021-11-26 15:49:36

На других языках

Эта страница на других языках

Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................