Est-il un moyen sûr d'utiliser un Nettoyant pour annuler l'inscription d'un auditeur?

0

La question

J'ai un Swing action de classe qui fonctionne comme suit:

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

L' FinalizeGuardian visé ici est actuellement mis en œuvre à l'aide de 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();
        }
    }
}

Donc, pour des raisons évidentes, je voudrais passer à l'utilisation de Cleaner pour cela.

Le premier essai était quelque chose comme ceci:

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

Le problème c'est que maintenant que l'objet ne devient jamais phantom accessible, parce que:

  • Cleaner détient lui-même une référence forte à cleanupLogic
  • cleanupLogic contient une référence à listener afin de supprimer l'auditeur
  • listener contient une référence à la classe d'action pour l'appeler updateEnabled sur elle
  • la classe d'action contient une référence à l' FinalizeGuardian afin de ne pas obtenir recueillies prématurément

Parce que le FinalizeGuardian lui-même ne devient jamais phantom accessible, le nettoyeur ne sera jamais appelé.

Donc ce que je voudrais savoir c'est, est-il un moyen de restructurer cet de suivre les règles nécessaires pour faire Cleaner fonctionne correctement que ne pas avoir d'encapsulation par le déplacement de l'auditeur à l'extérieur de ma classe de l'action?

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

La meilleure réponse

3

Tant que le FlavorListener est inscrit à une source de l'événement, il ne deviendra jamais inaccessible (tant que la source de l'événement est toujours accessible). Cela implique que l' PasteAction exemple, l'auditeur mises à jour ne sont jamais devenu inaccessible, que l'auditeur a une référence forte pour elle.

Le seul moyen de découpler leur accessibilité est de changer le port d'écoute, à seulement à maintenir un faible référence à l'objet de la mise à jour. Notez que lorsque vous utilisez un Cleaner au lieu de finalize()le FinalizeGuardian est obsolète.

Le code devrait ressembler à

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

…

Notez que les parties critiques ont été placés en static méthodes, pour faire de la capture accidentelle de l' this référence de l'impossible. Ces méthodes obtenir le minimum nécessaire pour faire leur travail, createListener ne reçoit qu'une faible référence à l'action et prepareCleanup devient le point de référence comme Objectcomme l'action de nettoyage ne doivent pas accéder aux membres de l'action, mais reçoivent de valeurs des paramètres.

Mais après avoir montré comment un nettoyage peut ressembler, je déconseille vivement l'utilisation de ce mécanisme, d'autant que le seul mécanisme de nettoyage. Ici, il n'est pas seulement une incidence sur la consommation de mémoire, mais aussi le comportement du programme, parce que, tant que les références n'ont pas été effacés, l'auditeur va garder s'informer et de tenir à jour un objet obsolète.

Depuis le garbage collector est déclenchée uniquement par les besoins de mémoire, il est parfaitement possible qu'il ne fonctionne pas ou ne se soucient pas de ces quelques objets, parce qu'il y a assez de mémoire libre, alors que le CPU est sous une charge lourde, à cause de beaucoup de l'obsolescence des auditeurs être occupé à mettre à jour les objets obsolètes (j'ai vu de tels scénarios dans la pratique).

Pour aggraver les choses, et, parallèlement, ramasseurs d'ordures, il est même possible que leur cycle de collecte de données à plusieurs reprises en conflit avec une réalité obsolète exécution de updateEnabled() déclenchée par l'auditeur (parce que la référence n'a pas encore été autorisées). Qui va empêcher activement la collecte des ordures de ces objets, même lorsque le garbage collector s'exécute et serait autrement recueillir.

En bref, un tel nettoyage ne doit pas compter sur le garbage collector.

2021-11-26 15:49:36

Dans d'autres langues

Cette page est dans d'autres langues

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................