package net.sf.openrocket.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that performs certain memory-management operations for debugging purposes.
* For example, complex objects that are being discarded and that should be garbage-collectable
* (such as dialog windows) should be registered for monitoring by calling
* {@link #collectable(Object)}. This will allow monitoring whether the object really is
* garbage-collected or whether it is retained in memory due to a memory leak.
* Only complex objects should be registered due to the overhead of the monitoring.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public final class MemoryManagement {
private static final Logger log = LoggerFactory.getLogger(MemoryManagement.class);
/** Purge cleared references every this many calls to {@link #collectable(Object)} */
private static final int PURGE_CALL_COUNT = 1000;
/**
* Storage of the objects. This is basically a mapping from the objects (using weak references)
* to
*/
private static List<MemoryData> objects = new LinkedList<MemoryData>();
private static int collectableCallCount = 0;
private static List<WeakReference<ListenerList<?>>> listenerLists = new LinkedList<WeakReference<ListenerList<?>>>();
private static int listenerCallCount = 0;
private MemoryManagement() {
}
/**
* Mark an object that should be garbage-collectable by the GC. This class will monitor
* whether the object actually gets garbage-collected or not by holding a weak reference
* to the object.
*
* @param o the object to monitor.
*/
public static synchronized void collectable(Object o) {
if (o == null) {
throw new IllegalArgumentException("object is null");
}
log.debug("Adding object into collectable list: " + o);
objects.add(new MemoryData(o));
collectableCallCount++;
if (collectableCallCount % PURGE_CALL_COUNT == 0) {
purgeCollectables();
}
}
/**
* Return a list of MemoryData objects corresponding to the objects that have been
* registered by {@link #collectable(Object)} and have not been garbage-collected properly.
* This method first calls <code>System.gc()</code> multiple times to attempt to
* force any remaining garbage collection.
*
* @return a list of MemoryData objects for objects that have not yet been garbage-collected.
*/
public static synchronized List<MemoryData> getRemainingCollectableObjects() {
for (int i = 0; i < 5; i++) {
System.runFinalization();
System.gc();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
purgeCollectables();
return new ArrayList<MemoryData>(objects);
}
/**
* Register a new ListenerList object. This can be used to monitor freeing of listeners
* and find memory leaks. The objects are held by a weak reference, allowing them to be
* garbage-collected.
*
* @param list the listener list to register
*/
public static synchronized void registerListenerList(ListenerList<?> list) {
listenerLists.add(new WeakReference<ListenerList<?>>(list));
listenerCallCount++;
if (listenerCallCount % PURGE_CALL_COUNT == 0) {
purgeListeners();
}
}
/**
* Return a list of listener list objects corresponding to the objects that have been
* registered by {@link #registerListenerList(ListenerList)} and have not been garbage-collected yet.
* This method first calls <code>System.gc()</code> multiple times to attempt to
* force any remaining garbage collection.
*
* @return a list of listener list objects that have not yet been garbage-collected.
*/
public static synchronized List<ListenerList<?>> getRemainingListenerLists() {
for (int i = 0; i < 5; i++) {
System.runFinalization();
System.gc();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
purgeListeners();
List<ListenerList<?>> list = new ArrayList<ListenerList<?>>();
for (WeakReference<ListenerList<?>> ref : listenerLists) {
ListenerList<?> l = ref.get();
if (l != null) {
list.add(l);
}
}
return list;
}
/**
* Purge all cleared references from the object list.
*/
private static void purgeCollectables() {
int origCount = objects.size();
Iterator<MemoryData> iterator = objects.iterator();
while (iterator.hasNext()) {
MemoryData data = iterator.next();
if (data.getReference().get() == null) {
iterator.remove();
}
}
log.debug(objects.size() + " of " + origCount + " objects remaining in discarded objects list after purge.");
}
/**
* Purge all cleared references from the object list.
*/
private static void purgeListeners() {
int origCount = listenerLists.size();
Iterator<WeakReference<ListenerList<?>>> iterator = listenerLists.iterator();
while (iterator.hasNext()) {
WeakReference<ListenerList<?>> ref = iterator.next();
if (ref.get() == null) {
iterator.remove();
}
}
log.debug(listenerLists.size() + " of " + origCount + " listener lists remaining after purge.");
}
/**
* A value object class containing data of a discarded object reference.
*/
public static final class MemoryData {
private final WeakReference<Object> reference;
private final long registrationTime;
private MemoryData(Object object) {
this.reference = new WeakReference<Object>(object);
this.registrationTime = System.currentTimeMillis();
}
/**
* Return the weak reference to the discarded object.
*/
public WeakReference<Object> getReference() {
return reference;
}
/**
* Return the time when the object was discarded.
* @return a millisecond timestamp of when the object was discarded.
*/
public long getRegistrationTime() {
return registrationTime;
}
}
}