Write-to-disk cache via WeakReferences?

Discussion in 'Java' started by Paul J. Lucas, Jul 23, 2005.

  1. 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
    Paul J. Lucas, Jul 23, 2005
    #1
    1. Advertising

  2. Paul J. Lucas wrote:
    >
    > 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
    --
    Unemployed English Java programmer
    Thomas Hawtin, Jul 23, 2005
    #2
    1. Advertising

  3. Thomas Hawtin <> wrote:

    > 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
    Paul J. Lucas, Jul 24, 2005
    #3
  4. Paul J. Lucas wrote:
    > Thomas Hawtin <> wrote:
    >
    >
    >>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?


    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.

    >>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.


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

    Tom Hawtin
    --
    Unemployed English Java programmer
    Thomas Hawtin, Jul 24, 2005
    #4
  5. Thomas Hawtin <> wrote:
    > Paul J. Lucas wrote:


    > > 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.


    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
    Paul J. Lucas, Jul 24, 2005
    #5
  6. Paul J. Lucas wrote:
    >
    > 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
    --
    Unemployed English Java programmer
    Thomas Hawtin, Jul 24, 2005
    #6
  7. Thomas Hawtin <> wrote:

    > 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;
    }
    Paul J. Lucas, Jul 25, 2005
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Indbond
    Replies:
    10
    Views:
    523
    John C. Bollinger
    Apr 23, 2004
  2. Paul J. Lucas
    Replies:
    2
    Views:
    369
  3. Paul J. Lucas
    Replies:
    5
    Views:
    400
  4. Ian Pilcher
    Replies:
    7
    Views:
    385
    Mike Schilling
    Jan 19, 2006
  5. Replies:
    12
    Views:
    496
    santosh
    Nov 15, 2006
Loading...

Share This Page