Logging when objects are garbage collected
Asked Answered
I

2

6

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.

Immingle answered 5/6, 2013 at 14:43 Comment(3)
It sounds like what you really want is a reference queue.Tibold
So I would replace the WeakIdentityHashMap and ConcurrentLinkedQueue with a ReferenceQueue but would still need the GarbageLogger thread to pop collected references and log them?Immingle
I've used the reference queue and concurrent map approach as a slightly tidier way of doing what I was doing before. If this was put in an answer I would accept it.Immingle
T
8

The traditional way to respond to garbage collection events is to register the WeakReferences 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.

Tibold answered 6/6, 2013 at 18:3 Comment(0)
G
3

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.

Glasses answered 5/6, 2013 at 15:2 Comment(2)
Yes the bottleneck on a synchonrized map would probably be an issue. Do you think the above solution (in a comment) with a reference queue would also be a bottleneck?... I supposed I would have to synchronize on adding to that.Immingle
@Immingle The reference queue would not present a bottleneck. This code might help you out - it combines a reference queue with a concurrent mapLichi

© 2022 - 2024 — McMap. All rights reserved.