4
\$\begingroup\$

Sometimes I need to ensure that some references won't be processed while examination from the associated ReferenceQueue. Generally at those moments I don't know reachability status of the referent.

The only way I've came up with:

class MyReference<V> extends SoftReference<V> {
    private boolean active = true;

    public MyReference(V referent, ReferenceQueue<? super V> queue) {
        super(referent, queue);
    }

    public void disable() {
        active = false;
    }

    public boolean isActive() {
        return active;
    }
}

// examination
Reference<? extends V> ref;
while ((ref = referenceQueue.poll()) != null) {
    @SuppressWarnings("unchecked")
    MyReference<V> myRef = (MyReference<V>) ref;
    if (myRef.isActive()) {
        // process ref
    }
}

Is this the right approach?

\$\endgroup\$
  • \$\begingroup\$ This is the best way I can think of to do it. \$\endgroup\$ – Jeffrey Jul 16 '13 at 3:57
2
\$\begingroup\$

I believe there is a flaw in your logic that is insurmountable......

SoftReferences, once they are added to the queue, have already lost the Referent instance. I cannot think of any reason why you would want to handle an 'active' queued (the value has been GC'd) softreference in any way differently from an 'inactive' one.

Once the reference has been queued it's too late to do anything about the instance it was softly referencing.

On the other hand, if you want to 'process' a referant, and make sure it is not GC'd during the processing, then jsut keep a reference to it:

V referent = softref.get();
if (referent == null) {
    // it's already been GC'd (and at some point we will see it poll'd from the Q)
    return;
}
// do things with our referent
// since our referent varaible is a hard-reference,
//      nothing can happen to the soft reference.
....

// then clear our reference (or return from our method,
// or clear our hard reference in some other way)
referent = null;
// after this the referent can (possibly) be GC'd again

EDIT after comments:

From the Javadoc for SoftReference:

Suppose that the garbage collector determines at a certain point in time that an object is softly reachable. At that time it may choose to clear atomically all soft references to that object and all soft references to any other softly-reachable objects from which that object is reachable through a chain of strong references. At the same time or at some later time it will enqueue those newly-cleared soft references that are registered with reference queues.

The GC will only nequeue newly cleared soft references. Further, from the SoftReference.get() method, we have:

Returns this reference object's referent. If this reference object has been cleared, either by the program or by the garbage collector, then this method returns null

Finally, here is a third-party 'reference' on 'references' ... heh. It appears to be quite good.


EDIT 2: how to keep a value 'active' (responding to comments)

If the requirement is to prevent a particular Soft reference referent from being GC'd, the answer is relatively simple: create a hard reference and keep it.... For example:

private static final IdentityHashMap<MyType, Object> active = new .....;
private static final Object token = new Object();

private static final boolean forceActive(SoftReference<MyType> reference) {
    MyType referent = softref.get();
    if (referent == null) {
        // it's already been GC'd ... and ...
        // if the reference was created with a ReferenceQueue, we can
        // expect to 'poll' the reference from that queue at some point
        // maybe we have already done that....
        return false;
    }
    // referent is now a hard reference, we are guaranteed that `reference`
    // is not on any Queue, and that it will not be GC'd (since we have a hard reference)

    // we want to keep our referent 'active', so we need to keep a hard reference:
    // the key in the identity hash map is our hard link.
    active.put(referent, token);
    // we exit our keep-active method, but the hard reference remains.
    return true;
}

private static final boolean deActive(SoftReference<MyType> reference) {
    MyType referent = softref.get();
    if (referent == null) {
        // it's already been GC'd ... and ... that means it was not
        // in our active map (otherwise it would not have been GC'd).
        return false;
    }
    // if the map contains the referent as a key, then it will return 'token'.
    // and we have now successfully removed the hard-reference to the referent.
    return token == active.remove(referent);
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ "Some time after the garbage collector determines that the reachability of the referent has changed to the value corresponding to the type of the reference, it will add the reference to the associated queue. At this point, the reference is considered to be enqueued." -- I haven't found in the docs clear statement that ref.get() == null for references in the queue. \$\endgroup\$ – leventov Nov 21 '13 at 10:15
  • 1
    \$\begingroup\$ No. Suppose referent is GC'd (ref.get() == null, but I don't know is the ref is already enqueued or not. If it isn't enqueued, I just need to "forget" the ref. Otherwise, I need to "remove" it from the queue (that was the purpose of MyReference class from the question). \$\endgroup\$ – leventov Nov 21 '13 at 18:05
  • 1
    \$\begingroup\$ @leventov Also, note, that if the referent is GC'd, the Reference will be have get() == null and also be enqueued 'at some point'... but you don't know where in the queue it will be, and there is only 1 method you can use on the queue, and that's poll() and that may not remove your Reference. \$\endgroup\$ – rolfl Nov 21 '13 at 18:46
  • 1
    \$\begingroup\$ No. I touch some refs and don't want to process them through the queue.poll(), but there are some other refs which follow the "normal" queue way. \$\endgroup\$ – leventov Nov 21 '13 at 21:14
  • 1
    \$\begingroup\$ Once you register a SoftReference with a queue there is no way to deregister it, other than to clear it's referent value. \$\endgroup\$ – rolfl Nov 21 '13 at 21:21
0
\$\begingroup\$

<while (... referenceQueue.poll()> is a busy loop. 98.5% of times, you will not want it. Opt for .remove instead.

Otherwise, it looks pretty good. If you need an additional functionality which is to asap ReleaseResources(), do this:

class MyReference<V> extends SoftReference<V> {
    public V GetObjAndReleaseIfNeeded() {
        V obj = get();
        if(obj === null) ReleaseResources();
    }
    public void ReleaseResources(){
        if(is_released)return;
        // release
        is_released = true;
    }

..and in the queue:

while (ref = referenceQueue.remove()) {
    @SuppressWarnings("unchecked")
    MyReference<V> myRef = (MyReference<V>) ref;
    myRef.ReleaseResources()
}

This will ensure that even if the GC takes forever to queue your ref, you'll still ReleaseResources() when the user calls GetObjAndReleaseIfNeeded().

Naturally, if the user does not call GetObjAndReleaseIfNeeded, your resources will be unreleased even while obj has been out of scope, all the way until the GC queues your ref and the queue has been polled, if ever.

A running GC is never guaranteed to ever queue a softreference.

In fact, a GC is never guaranteed to run, ever, thus even weakreferences are never guaranteed to be queued .

Note: Be careful of synchronization if getting ref and polling queue ain't on the same thread.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.