【问题标题】:How can I use WeakReferences for Listeners (Observer Pattern) to avoid memory leaks? [closed]Java - 带有弱引用侦听器的侦听器支持可以解决我的内存问题吗? [关闭]
【发布时间】:2015-03-24 21:37:14
【问题描述】:

带有弱引用侦听器的侦听器支持可以解决我的内存问题吗?

【问题讨论】:

  • 您需要将其表述为问题/答案。
  • StackOverflow 并不是真正写博客的地方。
  • @sp00m 是的,您可以回答自己的问题。不过,这里没有问题。
  • @aioobe 这样更好吗?

标签: java memory-management listener observer-pattern weak-references


【解决方案1】:

我自己做了一些检查(见下文)。你同意我的结论吗?

简介:

这里有 7 个不同的试验,展示了在观察者模型(侦听器模型)上下文中强引用和弱引用的使用和误用。我知道已经有很多关于弱引用的优缺点的帖子,但是即使在我研究了大量这些内容之后,我仍然觉得我需要用代码来感受它。我需要知道的是,在扩展 10-15 年的代码池并失去听众(非 gui 听众)时遇到堆泄漏。认为结果和代码可能对其他想要弄清楚这个主题的人有用。

欢迎评论/修改/替代!


结论:

始终使用以下两种策略之一:

  • 对侦听器的强引用,然后确保在侦听器“所有者”(或所有者所有者...)被删除并变得无法访问之前删除其中的每一个(下面的案例 C)。这意味着在几乎所有的类上严格实现(并使用!)“free()”或“destroy()”方法。
  • 对作为其所有者类上的字段的侦听器的弱引用(即,侦听器不应是本地方法变量)(下面的案例 E)。

如果您要创建一个供其他人使用,对侦听器的弱引用可能不是一个选项,因为在编译时和运行时 java 都没有任何方法检查在某个主题上注册的听众在另一端是否有强链接。请注意,与周围的许多讨论相反,该问题与侦听器是否是匿名类无关。这纯粹是听者如何连接到“另一端”的问题(不是在主体端而是在观察者端的端)。


这是我的控制台输出:

在一天结束时 - 没有惊喜,只是确认人们实际上会期待什么。如果侦听器在观察者模型的任一端都有强引用,那么它不会被垃圾收集。如果它只有弱引用,那么您的对象可能会在您想要它之前被垃圾收集(例如,请参见案例 G,其中 notifyCount 为 0)。

    A. WITHOUT connecting listeners
       At start, memory used = 281 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800288 KB
       After setting list of observes to null, memory used = 282 KB

    B. STRONG references to FIELD listeners
       At start, memory used = 286 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800291 KB
       After making change on subject (notifiedCount=100), memory used = 800292 KB
       After setting list of observes to null, memory used = 800292 KB

    C. STRONG references to FIELD listeners with REMOVE
       At start, memory used = 286 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800292 KB
       After making change on subject (notifiedCount=100), memory used = 800293 KB
       After removing listeners, memory used = 800292 KB
       After setting list of observes to null, memory used = 286 KB

    D. STRONG references to LOCAL listeners
       At start, memory used = 286 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800295 KB
       After making change on subject (notifiedCount=100), memory used = 800295 KB
       After setting list of observes to null, memory used = 800294 KB

    E. WEAKLY references to FIELD listeners
       At start, memory used = 287 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800297 KB
       After making change on subject (notifiedCount=100), memory used = 800297 KB
       After setting list of observes to null, memory used = 291 KB

    F. WEAKLY references to FIELD listeners with REMOVE
       At start, memory used = 287 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800297 KB
       After making change on subject (notifiedCount=100), memory used = 800297 KB
       After removing listeners, memory used = 800294 KB
       After setting list of observes to null, memory used = 288 KB

    G. WEAKLY references to LOCAL listeners
       At start, memory used = 287 KB
       After instantiation 100 observers with a double[1000000] each, memory used = 800297 KB
       After making change on subject (notifiedCount=0), memory used = 800297 KB
       After setting list of observes to null, memory used = 291 KB

我做了什么:

第 1 步:我创建了两个不同的侦听器支持类:

  • 对听众有经典的强烈引用:
public class ListenerSupportWithStrongReferences implements ListenerSupport {
    List&ltPropertyChangeListener&gt strongListeners = new ArrayList&ltPropertyChangeListener>

    public void addPropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        strongListeners.add(propertyChangeListener);
    }

    public void removePropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        strongListeners.remove(propertyChangeListener);
    }

    public void firePropertyChangeEvent(PropertyChangeEvent propertyChangeEvent) {
        for(PropertyChangeListener strongListener : strongListeners){
            strongListener.propertyChange(propertyChangeEvent);
        }
    }
}
  • 另一个对其听众的引用很弱:
public class ListenerSupportWithWeakReferences implements ListenerSupport {

    List&ltWeakReference&ltPropertyChangeListener&gt&gt weakListeners = 
        new ArrayList&ltWeakReference&ltPropertyChangeListener&gt&gt();

    public void addPropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        weakListeners.add(new WeakReference&ltPropertyChangeListener&gt(propertyChangeListener));
    }

    public void removePropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        for(WeakReference&ltPropertyChangeListener&gt weakReference : weakListeners){
            if(weakReference.get()==propertyChangeListener){
                weakListeners.remove(weakReference);
                break;
            }
        }

    }

    public void firePropertyChangeEvent(PropertyChangeEvent propertyChangeEvent) {
        for(WeakReference&ltPropertyChangeListener&gt weakReference : weakListeners){
            PropertyChangeListener propertyChangeListener = weakReference.get();
            if(propertyChangeListener!=null)
                propertyChangeListener.propertyChange(propertyChangeEvent);
        }
    }
}

两个支持类都实现了接口:

public interface ListenerSupport extends SubjectInterface {

    public void firePropertyChangeEvent(PropertyChangeEvent propertyChangeEvent);
}

进而扩展接口:

public interface SubjectInterface {

    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener);
    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener);
}

第 2 步: 我创建了一个具体的主题类,允许我通过构造函数控制侦听器支持的类型,并且我可以更改一个值,使用已实例化的任何侦听器支持来通知侦听器.

public class ConcreteSubject implements SubjectInterface {

    public static final String STRONG_LISTENERS = "STRONG_LISTENERS";
    public static final String WEAK_LISTENERS = "WEAK_LISTENERS";

    private final ListenerSupport listenerSupport;
    private int myValue;

    public ConcreteSubject(String typeOfListeners) {
        if(typeOfListeners.equals(STRONG_LISTENERS)){
            listenerSupport = new ListenerSupportWithStrongReferences();
        } else if(typeOfListeners.equals(WEAK_LISTENERS)){
            listenerSupport = new ListenerSupportWithWeakReferences();
        } else {
            throw new RuntimeException("Unknown type of listeners:"+typeOfListeners);
        }
    }

    public void addPropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        listenerSupport.addPropertyChangeListener(propertyChangeListener);
    }

    public void removePropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        listenerSupport.removePropertyChangeListener(propertyChangeListener);
    }

    private void fireMyValueHasChange(int oldValue, int newValue){
        PropertyChangeEvent propertyChangeEvent = 
            new PropertyChangeEvent(this, "SomeEvent", new Integer(oldValue), new Integer(newValue));
        listenerSupport.firePropertyChangeEvent(propertyChangeEvent);
    }

    public int getMyValue() {
        return myValue;
    }

    public void setMyValue(int myValue) {
        int oldValue = this.myValue;
        this.myValue = myValue;
        fireMyValueHasChange(oldValue, this.myValue);
    }
}

第 3 步: 我创建了一个带有大型双精度数组的观察类,以占用大量内存。我还让观察班能够为两个不同的听众服务: - 在观察者本身实例化时创建的字段监听器(因此观察者对其监听器有很强的引用)和 - 在返回它的方法中创建的侦听器(因此观察者没有对其侦听器的引用)。 我还创建了一个计数器来跟踪通知。

public class ObservingObject {

    public final static int DOUBLE_ARRAY_SIZE = 1000000;
    private double[] myDoubles = new double[DOUBLE_ARRAY_SIZE];
    private int notifiedCount = 0;

    private PropertyChangeListener fieldListener = new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent arg0) {
            notifiedCount++;
        }
    };

    public PropertyChangeListener getFieldPropertyChangeListener() {
        return fieldListener;
    }

    public PropertyChangeListener getLocalMethodPropertyChangeListener() {
        PropertyChangeListener localListenerInstance = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent arg0) {
                notifiedCount++;
            }
        };
        return localListenerInstance;
    }

    public int getNotifiedCount() {
        return notifiedCount;
    }
}

第 4 步: 最后,我创建了我的主类来尝试 7 种不同的“配置”:

  • 对听众的强/弱引用,
  • 字段/方法本地侦听器,
  • 有/没有移除监听器。

    上面列出了这个主要试验类的输出。请注意,我在计算使用的内存之前调用了垃圾收集器,以确保我看到的是实际挂起或使用的内存。

public class WeakReferenceListenerTrials {

    private static final String LOCAL_LISTENER = "local method listener";
    private static final String FIELD_LISTENER = "field listener";
    List&ltObservingObject&gt observers;

    private void runTials() {
        runTrial("A. WITHOUT connecting listeners", null, null, false);
        runTrial("B. STRONG references to FIELD listeners", 
                new ConcreteSubject(ConcreteSubject.STRONG_LISTENERS), FIELD_LISTENER, false);
        runTrial("C. STRONG references to FIELD listeners with REMOVE", 
                new ConcreteSubject(ConcreteSubject.STRONG_LISTENERS), FIELD_LISTENER, true);
        runTrial("D. STRONG references to LOCAL listeners", 
                new ConcreteSubject(ConcreteSubject.STRONG_LISTENERS), LOCAL_LISTENER, false);
        runTrial("E. WEAKLY references to FIELD listeners", 
                new ConcreteSubject(ConcreteSubject.WEAK_LISTENERS), FIELD_LISTENER, false);
        runTrial("F. WEAKLY references to FIELD listeners with REMOVE", 
                new ConcreteSubject(ConcreteSubject.WEAK_LISTENERS), FIELD_LISTENER, true);
        runTrial("G. WEAKLY references to LOCAL listeners", 
                new ConcreteSubject(ConcreteSubject.WEAK_LISTENERS), LOCAL_LISTENER, false);
    }

    private void runTrial(String titleText, 
            ConcreteSubject subject, String localOrFieldListeners, boolean removeListeners) {
        System.out.println("\n"+titleText);
        printMem("   At start");

        instantiateObserversAndConnectListenersToSubject(subject, localOrFieldListeners);
        printMem("   After instantiation 100 observers with a double["+ObservingObject.DOUBLE_ARRAY_SIZE+"] each");

        if(subject!=null){
            subject.setMyValue(1);
            int notifiedCount = 0;
            for(ObservingObject observingObject : observers){
                notifiedCount = notifiedCount + observingObject.getNotifiedCount();
            }
            printMem("   After making change on subject (notifiedCount="+notifiedCount+")");
        }

        if(removeListeners){
            removeListeners(subject, localOrFieldListeners);
            printMem("   After removing listeners");
        }

        observers = null;
        printMem("   After setting list of observes to null");
    }

    private void removeListeners(ConcreteSubject subject,
            String localOrFieldListeners) {
        if(localOrFieldListeners.equals(FIELD_LISTENER) && subject!=null){
            for(ObservingObject observingObject :observers){
                subject.removePropertyChangeListener(observingObject.getFieldPropertyChangeListener());
            }
        }
    }

    private void instantiateObserversAndConnectListenersToSubject(
            ConcreteSubject subject, String localOrFieldListeners) {
        observers = new ArrayList&ltObservingObject&gt();
        int observerCount = 100;
        for(int i = 0 ; i lt observerCount ; i++){
            ObservingObject observingObject = new ObservingObject();
            observers.add(observingObject);
            if(subject!=null){
                if(localOrFieldListeners.equals(FIELD_LISTENER)){
                    subject.addPropertyChangeListener(observingObject.getFieldPropertyChangeListener());
                } else if(localOrFieldListeners.equals(LOCAL_LISTENER)){
                    subject.addPropertyChangeListener(observingObject.getLocalMethodPropertyChangeListener());
                } else {
                    throw new RuntimeException("Unknow listener type");
                }
            }
        }
    }

    private void printMem(String string) {
        System.out.println(string+", memory used = "+getUsedMemoryInKB()+" KB");
    }

    private static int getUsedMemoryInKB() {
        // Start by garbage collect
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();

        // Calculate 'used memory' as difference between total and free
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;
        int usedMemoryInKB = (int)(usedMemory/1000);
        return usedMemoryInKB;
    }

    public static void main(String[] args) {
        new WeakReferenceListenerTrials().runTials();
    }
}

就是这样。希望对其他人有用。是给我的。

【讨论】:

    猜你喜欢
    • 2013-01-04
    • 1970-01-01
    • 2021-08-07
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    • 2014-03-04
    相关资源
    最近更新 更多