My application logs the usage of certain objects - my setup uses AspectJ to identify the contexts I'm interested in and logs these usages. I later load the log files for analysis, but for efficiency reasons it's useful to know when an object is no longer reachable.
My current approach is to log the objects I'm interested in with a 'garbage logger', which then creates a 'saver' object containing the object's identity hash code and stores this in a weak hash map. The idea being that when the object is collected, the saver object will be removed from the weak hash map and collected, thus running code to log the identity hash code of the collected object. I use a separate thread and queue to prevent causing a bottleneck in the garbage collector. Here's the garbage logger code:
public class GarbageLogger extends Thread {
private final Map<Object,Saver> Savings =
Collections.synchronizedMap(new WeakIdentityHashMap<Object,Saver>());
private final ConcurrentLinkedQueue<Integer> clearTheseHash =
new ConcurrentLinkedQueue<Integer>();
public void register(Object o){
Savings.put(o,new Saver(System.identityHashCode(o));
}
private class Saver{
public Saver(int hash){ this.hash=hash;}
private final int hash;
@Override
public void finalize(){
clearTheseHash.add(hash);
}
}
@Override
public void run(){
while(running){
if((clearTheseHash.peek() !=null)){
int h = clearTheseHash.poll();
log(h);
}
else sleep(100);
}
}
// logging and start/end code omitted
}
My problem is that this seems very convoluted and, because weak hash map won't necessarily clear its entries unless space is needed, I might be waiting a long time after the object is collected before recording it. Basically, I'm looking for a better way to achieve this.
Note - I am monitoring arbitrary objects and have no control over their creation so cannot override their finalize methods.
In order to study and optimize garbage collection's impact on an application's performance, one has to enable Garbage Collection Logging. Besides that, garbage collection logs can be used to troubleshoot memory-related problems in the application.
It is possible to define a method that will be called just before an object's final destruction by the garbage collector. This method is called finalize( ), and it can be used to ensure that an object terminates cleanly.
The gc() method is used to invoke the garbage collector to perform cleanup processing. The gc() is found in System and Runtime classes.
The traditional way to respond to garbage collection events is to register the WeakReference
s with a ReferenceQueue
, in which they'll automatically be enqueued when the referenced object is GC'd, and then to periodically poll the ReferenceQueue
(possibly in a separate thread) to do cleanups.
A standard trick is to extend the WeakReference
class, attaching any additional information you want to know when the cleanup occurs, and then to cast the WeakReference
objects in the ReferenceQueue
back to your MyWeakReference
to get the information.
An somewhat simpler alternative that might give quicker results (and that also alleviates the potential bottleneck on Savings
) is
private final ConcurrentMap<WeakReference, Integer> savings = new ConcurrentHashMap<>();
public void run() {
while(running) {
for(WeakReference reference : savings.keySet()) {
if(reference.get() == null) {
log(savings.remove(reference));
}
}
sleep(1000);
}
}
The downside is that you must continually iterate through the map in order to find cleared references, but the advantage is a simpler implementation, and reference.get() == null
will be true as soon as the object is cleared (whereas there may be a delay in registering a cleared object in a WeakHashMap
). Using a ConcurrentMap
alleviates the bottleneck that may be created by your use of a Collections.synchronizedMap
, and more importantly prevents the for-each loop from throwing a ConcurrentModificationException
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With