我有一个 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();
}
}
就是这样。希望它对其他人有用。这是给我的。