¿Hay una forma segura para que use un Limpiador para cancelar el registro de un oyente?

0

Pregunta

Tengo un Swing de clase de acción que funciona de la siguiente manera:

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

El FinalizeGuardian se hace referencia aquí es actualmente implementado el uso 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();
        }
    }
}

Así, por obvias razones, me gustaría cambiar a la utilización de Cleaner para esto.

El primer intento fue algo como esto:

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

El problema es que ahora el objeto nunca se convierte en fantasma accesible, porque:

  • Cleaner en sí mismo tiene una fuerte referencia cleanupLogic
  • cleanupLogic contiene una referencia a listener con el fin de eliminar el oyente
  • listener contiene una referencia a la clase de acciones, con el fin de llamar updateEnabled en él
  • la clase de acción contiene una referencia a la FinalizeGuardian así que no se recogen de forma prematura

Debido a que el FinalizeGuardian en sí nunca se convierte en fantasma accesible, el limpiador nunca será llamado.

Lo que me gustaría saber es, ¿hay una manera de reestructurar este para seguir las reglas necesarios para hacer Cleaner el trabajo correctamente, que no implican la ruptura de la encapsulación moviendo el detector fuera de mi clase de acción?

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

Mejor respuesta

3

Mientras el FlavorListener está inscrita en un evento de origen, que nunca va a ser inalcanzable (siempre y cuando el origen del evento sigue siendo accesible). Esto implica que la PasteAction instancia que el oyente actualizaciones también nunca se vuelvan inaccesibles, como el detector tiene una fuerte referencia a ella.

La única manera de disociar su accesibilidad es cambiar el oyente, para mantener sólo una débil referencia al objeto se actualiza. Tenga en cuenta que cuando usted está utilizando una Cleaner en lugar de finalize()el FinalizeGuardian es obsoleto.

El código serí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);
    }

…

Tenga en cuenta que las partes críticas han sido puestos en static métodos, para hacer la captura accidental de la this referencia imposible. Estos métodos de obtener el mínimo necesario para hacer su trabajo, createListener sólo recibe una débil referencia a la acción y prepareCleanup se presenta el referente como Objectcomo la acción de limpieza no deben tener acceso a alguno de los miembros de la acción, pero los valores como parámetros independientes.

Pero después de la demostración, cómo un limpiador de uso puede parecer, tengo que desaconseja la utilización de este mecanismo, especialmente como el único mecanismo de limpieza. Aquí, no es sólo que afectan el consumo de memoria, pero también el comportamiento del programa, porque, siempre y cuando las referencias no se han resuelto, el oyente va consiguiendo mantener informado y mantener la actualización de un objeto obsoleto.

Desde el recolector de basura se activa sólo por necesidades de memoria, es perfectamente posible que no se ejecuta o no se preocupa de estos pocos objetos, porque no hay suficiente memoria libre, mientras la CPU está en condiciones de carga pesada, debido a un montón de obsoletos los oyentes de ser ocupado para actualizar objetos obsoletos (he visto este tipo de escenarios en la práctica).

Para empeorar las cosas, con la consiguiente recolectores de basura, incluso es posible que su ciclo de colección en repetidas ocasiones se superpone con una realidad obsoleta de la ejecución de updateEnabled() provocada por el oyente (porque la referencia no se ha borrado todavía). Que activamente de evitar la recolección de estos objetos, incluso cuando el recolector de basura se ejecuta y de otra manera recopilar ellos.

En definitiva, tal limpieza no deben confiar en el recolector de basura.

2021-11-26 15:49:36

En otros idiomas

Esta página está en otros idiomas

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