Existe uma maneira segura de usar o Limpador de para anular o registo de um ouvinte?

0

Pergunta

Eu tenho um Balanço de classe de ação que funciona da seguinte forma:

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

O FinalizeGuardian aqui referida está actualmente implementado usando 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();
        }
    }
}

Assim, por razões óbvias, eu gostaria de passar a usar Cleaner para este.

A primeira tentativa foi algo como isto:

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

O problema é que, agora, o objeto nunca se torna fantasma acessível, porque:

  • Cleaner si tem uma forte referência para cleanupLogic
  • cleanupLogic contém uma referência para listener a fim de remover o ouvinte
  • listener mantém uma referência para a classe de ação, a fim de chamada updateEnabled sobre ele
  • a ação de classe possui uma referência para o FinalizeGuardian para que ele não fique colhidos prematuramente

Porque o FinalizeGuardian si nunca se torna fantasma acessível, o aspirador nunca será chamado.

Então, o que eu gostaria de saber é se existe uma maneira de reestruturar a que este siga as regras necessárias para fazer Cleaner o trabalho corretamente que não envolvem a quebra de encapsulamento ao mover o ouvinte para fora da minha classe de ação?

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

Melhor resposta

3

Enquanto o FlavorListener é registrado em um evento de origem, ele nunca vai se tornar inacessível (desde que a origem do evento é ainda acessível). Isto implica que o PasteAction instância que o ouvinte atualizações também nunca se tornar inacessível, como o ouvinte tem uma forte referência a ele.

A única maneira de dissociar a sua acessibilidade é para alterar o ouvinte, para apenas manter um fraco referência para o objecto de actualizações. Observe que, quando você estiver usando um Cleaner em vez de finalize()o FinalizeGuardian é obsoleto.

O código ficaria como

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

…

Observe que as peças essenciais têm sido colocados em static métodos, para fazer a captura acidental de this referência impossível. Estes métodos de obter o mínimo necessário para fazer o seu trabalho, createListener só recebe uma referência fraca para a ação e prepareCleanup fica o referente como Objectcomo a ação de limpeza não devem acessar qualquer um dos membros da ação, mas receber os valores necessários como parâmetros separados.

Mas depois de mostrar, como um limpador de uso pode parecer, eu tenho a desencorajar fortemente a partir da utilização deste mecanismo, especialmente por ser o único mecanismo de limpeza. Aqui, não apenas afetam o consumo de memória, mas também o comportamento do programa, porque, desde que as referências não foram compensados, o ouvinte vai conseguir manter informado e manter a atualização de um objeto obsoleto.

Desde o coletor de lixo é acionado somente por necessidades de memória, é perfeitamente possível que ele não é executado ou não se importam com estes poucos objetos, porque não há memória livre suficiente, enquanto a CPU está sob carga pesada, por causa de muita obsoleto ouvintes a ser ocupado para atualizar os objetos obsoletos (eu vi esses cenários de prática).

Para piorar a situação, com simultânea coletores de lixo, é até possível que o seu ciclo de coleta várias vezes, sobrepõe-se, na verdade, um obsoleto execução de updateEnabled() acionado pelo ouvinte (porque a referência não foi finalizada). O que irá evitar ativamente da coleta de lixo desses objetos, mesmo quando o coletor de lixo é executado e, caso contrário, iria recolhê-los.

Em suma, tais limpeza não devem confiar em que o coletor de lixo.

2021-11-26 15:49:36

Em outros idiomas

Esta página está em outros idiomas

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