C'è un modo sicuro per utilizzare Detergente per annullare la registrazione di un ascoltatore?

0

Domanda

Ho un Altalena azione di classe, che funziona come segue:

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

Il FinalizeGuardian di cui qui è attualmente implementato utilizzando 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();
        }
    }
}

Così, per ovvi motivi, vorrei passare all'utilizzo di Cleaner per questo.

Il primo tentativo è stato qualcosa di simile a questo:

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

Il problema è che ora l'oggetto non diventa mai phantom raggiungibile, perché:

  • Cleaner di per sé ha un forte riferimento per cleanupLogic
  • cleanupLogic contiene un riferimento a listener al fine di rimuovere l'ascoltatore
  • listener contiene un riferimento all'azione di classe, in ordine di chiamata updateEnabled su di esso
  • l'azione di classe contiene un riferimento all' FinalizeGuardian in modo che non ottenere raccolti prematuramente

Perché il FinalizeGuardian di per sé non diventa mai phantom raggiungibile, il pulitore non verrà mai chiamato.

Quindi quello che vorrei sapere è, c'è un modo per ristrutturare questo per seguire le regole necessarie per rendere Cleaner lavoro, correttamente, che non comportano la rottura di incapsulamento spostando l'ascoltatore al di fuori della mia azione di classe?

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

Migliore risposta

3

Come lungo come il FlavorListener è registrato in occasione di un evento di origine, non potrà mai diventare irraggiungibile (come lungo come l'origine dell'evento è ancora raggiungibile). Questo implica che il PasteAction istanza che l'ascoltatore aggiornamenti sarà mai anche diventare irraggiungibile, come l'ascoltatore ha un forte riferimento ad esso.

L'unico modo per scindere la loro raggiungibilità è quello di cambiare l'ascoltatore, solo per mantenere un debole riferimento all'oggetto si aggiorna. Si noti che quando si utilizza un Cleaner invece di finalize()il FinalizeGuardian è obsoleto.

Il codice sarebbe simile a

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

…

Nota che le parti critiche sono state poste in static metodi, per rendere la cattura accidentale di this riferimento impossibile. Questi metodi di ottenere il minimo necessario per fare il loro lavoro, createListener riceve solo un debole riferimento per l'azione e prepareCleanup diventa il referente come Objectcome l'azione di pulizia non devono accedere a tutti i membri di azione, ma ricevere la necessaria valori come parametri distinti.

Ma dopo che, come un detergente per uso può sembrare, ho a scoraggiare fortemente l'utilizzo di questo meccanismo, soprattutto come l'unico meccanismo di pulizia. Qui, non è solo che influenzano il consumo di memoria, ma anche il comportamento del programma, perché, fintanto che i riferimenti non sono stati cancellati, l'ascoltatore sarà mantenere sempre informati e mantenere l'aggiornamento a un obsoleti oggetto.

Dal momento che il garbage collector è innescata da esigenze di memoria, è perfettamente possibile che non funziona o non si cura di questi pochi oggetti, perché non c'è abbastanza memoria libera, mentre la CPU è sotto carico pesante, a causa di un sacco di obsoleti con gli ascoltatori di essere occupato per aggiornare gli oggetti obsoleti (ho visto questi scenari, in pratica).

A peggiorare le cose, con concomitante garbage collector, è anche possibile che il loro ciclo di raccolta più volte si sovrappone a una realtà obsoleti con esecuzione di updateEnabled() innescato da un ascoltatore (perché il riferimento non è stata cancellata di sicurezza). Che prevenire attivamente la spazzatura raccolta di questi oggetti, anche quando il garbage collector corre, altrimenti li raccolgono.

In breve, tale pulizia non devono fare affidamento su garbage collector.

2021-11-26 15:49:36

In altre lingue

Questa pagina è in altre lingue

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