Может ли слушатель поддерживать слабые ссылки слушателей решить мои проблемы с памятью?Поддержка Java-слушателей со слабыми реферированными ресиверами решает проблемы с памятью?
ответ
Я сделал немного, чтобы проверить это сам (см. Ниже). Согласны ли вы с моими выводами?
Введение:
Вот 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 различных "конфигураций" из:
- сильных/слабых ссылок на слушателей,
- поле/метод локального слушатели,
с/без rempoving слушателей.
Выпуск этого основного класса испытаний приведен выше. Обратите внимание, что я вызываю сборщик мусора перед вычислением памяти, используемой для обеспечения того, что то, что я вижу, - это фактически зависание или использование памяти.
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();
}
}
Вот так. Надеюсь, что это полезно для других. Это было для меня.
Вам нужно сформулировать это как вопрос/ответ. – aioobe
StackOverflow на самом деле не место для написания блога. – ceejayoz
http: //blog.stackoverflow.com/2011/07/the-ok-to-ask-and-answer-your-own-questions/ – sp00m