如何使用侦听器的弱引用(观察者模式)来避免内存泄漏



我有一个 10-15 年的代码池,它通过保留观察者对对象的强引用来泄漏内存。这些对象不会被垃圾回收。

我正在寻找一种通过使用弱引用来防止这种情况的解决方案。

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

介绍:

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

欢迎评论/修正/替代方案!


结论:

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

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

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


这是我的控制台输出:

归根结底 - 没有惊喜,而只是确认人们实际上的期望。如果侦听器在观察者模型的任一端都有强引用,则不会对其进行垃圾回收。如果它只有弱引用,那么你的对象可能会在你想要它之前被垃圾回收(参见示例案例 G,其中 notifiedCount 为 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<PropertyChangeListener> strongListeners = new ArrayList<PropertyChangeListener>
    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<WeakReference<PropertyChangeListener>> weakListeners = 
        new ArrayList<WeakReference<PropertyChangeListener>>();
    public void addPropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        weakListeners.add(new WeakReference<PropertyChangeListener>(propertyChangeListener));
    }
    public void removePropertyChangeListener(
            PropertyChangeListener propertyChangeListener) {
        for(WeakReference<PropertyChangeListener> weakReference : weakListeners){
            if(weakReference.get()==propertyChangeListener){
                weakListeners.remove(weakReference);
                break;
            }
        }
    }
    public void firePropertyChangeEvent(PropertyChangeEvent propertyChangeEvent) {
        for(WeakReference<PropertyChangeListener> 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步:我创建了一个具体的主题类,允许我通过构造函数控制listerner支持的类型,并且可以更改一个值,该值将使用已实例化的任何侦听器支持通知侦听器。

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

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

相关内容

  • 没有找到相关文章

最新更新