Write-to-disk cache via WeakReferences?

P

Paul J. Lucas

So the idea is to use WeakReferences to objects. When the GC
decides to GC said objects, I'd like a thread to be notified
via a ReferenceQueue that the objects are about to be GC'd. The
thread can then write them out to disk. The upshot of all this
would be to implement a disk cache for objects.

The problem is, in reading the documentation for WeakReference,
that all WeakReferences are cleared prior to being enqueued
onto ReferenceQueues. That means the code that does either the
poll() or remove() will always get a reference with null for
the referrent value of get().

That's annoying. Ideally, I want the thread to be able to get
at the object just before it's GC'd so it can be written out to
disk.

So is it possible to do something like I want (i.e., be given a
reference to an object about to be GC'd so I can do one last
thing with it)? If so, how?

- Paul
 
T

Thomas Hawtin

Paul said:
The problem is, in reading the documentation for WeakReference,
that all WeakReferences are cleared prior to being enqueued
onto ReferenceQueues. That means the code that does either the
poll() or remove() will always get a reference with null for
the referrent value of get().

Two options.

Use a proxy interface to your resource. Create a class that extends
WeakReference with the weak reference to the proxy and a strong
reference to the resource. Sometime after the Proxy is collected, the
WeakReference pops up on the reference queue, and you can get the strong
reference to the resource from that.

Proxy ----> Resource
^ ^
| |
{weak}| |{strong}
| |
Ref extends |
WeakReference<Proxy> -+

Alternatively use a finaliser. When finalize is called, resurrect the
resource by putting it on a queue. Have a thread pulling from the queue
and saving to disc.

(Notes about finalisers: It's best to put the finalize method on a guard
object with a bidirectional reference to the resource, as finalize
doesn't play nicely with inheritance. Certainly never delcare finalize
as anything other than protected and throws Throwable, and always call
the super with try/finally even if it is not overriden by a superclass
(yet). You shouldn't do much in the finaliser as you will block the
finalise thread(s) work. An object will finalise only once. It's your
responsibility to make sure finalisers are thread-safe, typically by
synchronising all methods. Finalisers have a bad reputation and you
could get yourself into some pointless conversations.)

Tom Hawtin
 
P

Paul J. Lucas

Thomas Hawtin said:
Use a proxy interface to your resource. Create a class that extends
WeakReference with the weak reference to the proxy and a strong
reference to the resource. Sometime after the Proxy is collected, the
WeakReference pops up on the reference queue, and you can get the strong
reference to the resource from that.

Proxy ----> Resource
^ ^
| |
{weak}| |{strong}
| |
Ref extends |
WeakReference<Proxy> -+

What is the upper, horizontal arrow? A strong or weak
references?

BTW: should it be a WeakReference or a SoftReference?
Alternatively use a finaliser. When finalize is called, resurrect the
resource by putting it on a queue. Have a thread pulling from the queue
and saving to disc.

The problem with that approach is that it's "intrusive" into the
class of object that is the resource, i.e., you can't do this
with any old object: it has to be one that has a finalizer that
does the right thing.

- Paul
 
T

Thomas Hawtin

Paul said:
What is the upper, horizontal arrow? A strong or weak
references?

Strong. You don't want the resource disappearing while you still have
the proxy alive.
BTW: should it be a WeakReference or a SoftReference?

Almost certainly soft. Weak references are useful if the cache is
extremely marginal (and then the cache will probably only make
significant differences in microbenchmarks). You might want to use a
weak reference if you know it is unlikely that the resource will be used
again anytime soon (usually a poor bet). I just stuck with the original
choice.
The problem with that approach is that it's "intrusive" into the
class of object that is the resource, i.e., you can't do this
with any old object: it has to be one that has a finalizer that
does the right thing.

Yup, although you can put the finaliser (guard) on a proxy.

Tom Hawtin
 
P

Paul J. Lucas

Thomas Hawtin said:
Paul J. Lucas wrote:

Strong. You don't want the resource disappearing while you still have
the proxy alive.

But if the class derived from WeakReference itself has a strong
reference to the object, why does the proxy object need one
also?

I would think that the proxy object could be a class like:

class Sacrifice {
// yes, empty
}

and the class derived from WeakReference has a weak reference to
it. The only reason for having a Sacrifice object is to be
sacrificed to the GC: when the GC wants to reclaim it, the
class derived from weak referernce referring to it gets
enqueued, i.e., it only serves as the trigger mechanism. The
class derived from WeakReference has its own hard reference to
the object.

Or am I missing something?

- Paul
 
T

Thomas Hawtin

Paul said:
But if the class derived from WeakReference itself has a strong
reference to the object, why does the proxy object need one
also?

I would think that the proxy object could be a class like:

class Sacrifice {
// yes, empty
}

and the class derived from WeakReference has a weak reference to
it. The only reason for having a Sacrifice object is to be
sacrificed to the GC: when the GC wants to reclaim it, the
class derived from weak referernce referring to it gets
enqueued, i.e., it only serves as the trigger mechanism. The
class derived from WeakReference has its own hard reference to
the object.

I think I'm being confusing with my proxies and guards. The guards are
for use with finalisers and aren't important here.

The situation in code looks something like this (only with better names):

public class Resource {
public void doStuff() { ... }
}
public class Proxy {
private final Resource target;
Proxy(Resource target) {
if (target == null) {
throw new NullPointerException();
}
this.target = target;
}
public void doStuff() {
target.doStuff();
}
}
class Ref extends Reference<Proxy> {
private final Resource resource;
public Ref(Proxy proxy, Resource resource) {
super(proxy);
this.resource = resource;
}
Resource getResource() {
return resource;
}
}
class Saver implements Runnable {
...
public void run() {
for (;;) {
Ref ref = (Ref)queue.remove();
saveToDisk(ref.getResource());
}
}
}

The client code uses Proxy rather than Resource.

Tom
 
P

Paul J. Lucas

Thomas Hawtin said:
The situation in code looks something like this (only with better names):

public class Resource {
public void doStuff() { ... }
}
public class Proxy {
private final Resource target;
Proxy(Resource target) {
if (target == null) {
throw new NullPointerException();
}
this.target = target;
}
public void doStuff() {
target.doStuff();
}
}
class Ref extends Reference<Proxy> {
private final Resource resource;
public Ref(Proxy proxy, Resource resource) {
super(proxy);
this.resource = resource;
}
Resource getResource() {
return resource;
}
}
class Saver implements Runnable {
...
public void run() {
for (;;) {
Ref ref = (Ref)queue.remove();
saveToDisk(ref.getResource());
}
}
}

Where did the SoftReference go?
You never create/pass a ReferenceQueue anywhere.
What about my code below?

- Paul


public interface StrongReference {
Object getAndClearStrong();
}

public class SoftStrongReference extends SoftReference
implements StrongReference {

public SoftStrongReference( Object referrent, ReferenceQueue refQ ) {
super( new Sacrifice(), refQ );
m_referrent = referrent;
}

public Object getAndClearStrong() {
final Object temp = m_referrent;
m_referrent = null;
return temp;
}

private static final class Sacrifice {
}

private Object m_referrent;
}

public interface ObjectSaver {
void save( Object obj );
}

public class ReferenceQueueManager extends Thread {

public ReferenceQueueManager( ObjectSaver saver ) {
setDaemon( true );
m_saver = saver;
m_refQ = new ReferenceQueue();
}

public void kill() {
m_killed = true;
interrupt();
}

public void run() {
while ( !m_killed ) {
try {
final StrongReference ref = (StrongReference)m_refQ.remove();
m_saver.save( ref.getAndClearStrong() );
}
catch ( InterruptedException e ) {
// do nothing
}
}
}

private boolean m_killed;
private final ObjectSaver m_saver;
private final ReferenceQueue m_refQ;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,023
Latest member
websitedesig25

Latest Threads

Top