Existuje bezpečný způsob, jak používat Cleaner odregistrovat listener?

0

Otázka

Mám Houpačka akce třídy, která funguje následovně:

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

Na FinalizeGuardian jen zde je v současné době realizován pomocí 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();
        }
    }
}

Takže, z pochopitelných důvodů, rád bych přejít na používání Cleaner pro tohle.

První pokus byl něco jako tohle:

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

Problém je, že nyní objekt nikdy se stává fantom dosažitelné, protože:

  • Cleaner sama o sobě má silný odkaz na cleanupLogic
  • cleanupLogic obsahuje odkaz na listener za účelem odstranění posluchače
  • listener drží odkaz na akční třídy v zájmu volat updateEnabled na to
  • akce třídy obsahuje odkaz na FinalizeGuardian tak, že to nemá se shromažďují předčasně

Protože FinalizeGuardian sám nikdy se stává fantom dosažitelné, tím čistší bude nikdy být nazýván.

Takže to, co bych rád věděl je, že existuje způsob, jak restrukturalizovat toto dodržovat pravidla nutná, aby se Cleaner pracovat správně, že nemusí zahrnovat porušení zapouzdření pohybem posluchače mimo můj akční třídy?

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

Nejlepší odpověď

3

Tak dlouho, jak FlavorListener je registrován na akci zdroj, to se nikdy nestane nedostupný (tak dlouho, dokud zdroj události je stále dosažitelné). To znamená, že PasteAction stupně, který posluchače aktualizace bude také nikdy stát nedosažitelné, jako posluchač má silné odkaz na něj.

Jediný způsob, jak oddělit jejich dosažitelnost je pro změnu posluchače, pouze udržovat slabý odkaz na objekt aktualizuje. Všimněte si, že když používáte Cleaner místo finalize(), FinalizeGuardian je zastaralé.

Kód bude vypadat

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

…

Všimněte si, že kritické součásti byly umístěny do static metody, aby se v případě náhodného zachycení this referenční nemožné. Tyto metody získat nezbytné minimum, aby dělat svou práci, createListener pouze přijímá slabý odkaz na akce a prepareCleanup dostane referent jako Objectjako čisticí účinek, nesmí být přístup k žádné členy akce, ale získat potřebné hodnoty jako samostatné parametry.

Ale poté, co ukazuje, jak čistší použití může vypadat, musím důrazně odradit od používání tohoto mechanismu, a to zejména jako jedinou vyčištění mechanismu. Tady, to je nejen ovlivňuje spotřebu paměti, ale také chování programu, protože tak dlouho, jak odkazy nebyly vymazány, bude posluchač stále informováni a udržet aktualizaci zastaralého objektu.

Od garbage collector je pouze vyvolána paměť potřebuje, to je dokonale možné, že to nefunguje, nebo se nestará o těchto několik objektů, protože není dostatek volné paměti, zatímco CPU je pod zátěží, protože hodně zastaralé posluchače, že je zaneprázdněn, aktualizovat zastaralé objekty (viděl jsem takové scénáře v praxi).

Aby to bylo ještě horší, s souběžné sběrači odpadků, je i možné, že jejich sbírka cyklu, opakovaně se překrývá s vlastně zastaralé provedení updateEnabled() spouští posluchače (protože referenční nebyla vymazána zatím). Který se bude aktivně bránit sběr odpadků z těchto objektů, i když garbage collector běží a které by jinak sbírat je.

Stručně řečeno, takové čištění by se neměla spoléhat na garbage collector.

2021-11-26 15:49:36

V jiných jazycích

Tato stránka je v jiných jazycích

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