J
John Ersatznom
It's come up several times lately: subfoo. Besides String's substring
method, a Subset class was proposed here recently that would provide a
subset backed by a Set, and a recent mention of image cropping naturally
suggests representing a cropped image by using the source image as a
backer and the added memory consumption is then only the bounds.
The naive implementations (including of String's substring method) tend
to have a problem: the parent object cannot be garbage collected as long
as the child object is floating around free.
I've come up with a fix to that, but it has a cost -- the object has to
be wrapped by one that has a finalizer, which impacts GC performance.
Fortunately the usual need is on larger images, strings, and the like
that are generally long-lived.
* First, don't make the subfoo implementation an inner class of the
backer, or at least only make it a static nested class. A true
inner class instance won't let its parent be garbage collected.
* Next, have the subfoo hold only a weak reference to its parent.
* Now comes the tricky part.
public class WeakSubFoo {
private class Holder {
public Foo foo;
public Holder (Foo foo) { this.foo = foo; }
protected void finalize () {
backerDead(foo);
}
}
private WeakReference<Holder> backer;
private Foo replacement = null;
// whatever else
public WeakSubFoo (Foo backer, other_args) {
this.backer = new WeakReference(new Holder(backer));
// whatever else
}
void backerDead (Foo foo) {
this.replacement = <actually copy relevant part of foo>
// Adjust bounds, e.g. from left=13, top=17, width=32,
// height=32, parentWidth = 1024 to left=0, top=0,
// width=32, height=32, parentWidth = 32 or from
// start=17, end=42 to end -= start, start=0
backer = null;
}
private Foo getBackingFoo () {
Holder h = backer.get();
if (h != null) return h.foo;
while (replacement == null) {
try {
sleep(1000);
} catch (InterruptedException e) {
// Ignore.
}
}
return replacement;
}
// Other logic, using getBackingFoo
}
The major issue I see is that getBackingFoo has to block if it gets
called after the reference is cleared but before the backer is finalized
by the GC. In theory that could be a while. I don't see any easy way to
avoid this, because I don't see any way to actually get a reference to a
weakly-reachable object as soon as it's discovered to be only
weakly-reachable by the GC.
If the backing object is of interface type, this alternative exists:
public final class BackingFoo implements Foo {
private Foo myFoo;
public BackingFoo (Foo foo) { myFoo = foo; }
public Foo getFoo () { return myFoo; }
// Punt implementation of Foo interface to myFoo
}
public class WeakSubFoo {
private WeakReference<BackingFoo> backerRef;
private ReferenceQueue<BackingFoo> q
= new ReferenceQueue<BackingFoo>();
private Foo backer = foo;
// whatever else
public WeakSubFoo (BackingFoo backer, other_args) {
this.backer = backer.getFoo();
this.backerRef
= new WeakReference<BackingFoo>(backer, q);
// whatever else
}
private Foo getBackingFoo () {
if (q.poll() == backerRef) {
backerRef = null;
q = null; // make these reclaimable
backer = <copy of relevant subset of backer>
// Adjust bounds, e.g. from left=13, top=17,
// width=32, height=32, parentWidth = 1024 to
// left=0, top=0, width=32, height=32,
// parentWidth = 32 or from start=17, end=42
// to end -= start, start=0
}
return backer;
}
// Other logic, using getBackingFoo
}
The key to making this work is for client code to use BackingFoo and
WeakSubFoo but never just Foo. All raw Foos should be immediately
wrapped in a BackingFoo. When a BackingFoo with a WeakSubFoo becomes no
longer strongly reachable outside WeakSubFoos, it has in fact become
only weakly reachable. When getBackingFoo eventually is called in a
WeakSubFoo, it finds the reference backerRef on q and responds by
copying the relevant part of backer, which holds the underlying Foo
itself. E.g. a stringalike's 17 to 42 substring to a new stringalike
with only those 26 characters, and the substring position data changed
appropriately. The strong reference to the parent Foo is now gone, and
as long as there are no others (they were held only through references
to the single BackingFoo that is now only weakly reachable), the Foo
itself is now only weakly reachable and is reclaimable.
Besides the need to use exactly one BackingFoo per Foo and avoid holding
direct references to the underlying Foo, there is another caveat: if a
WeakSubFoo goes unused for a long time the backing Foo doesn't become
reclaimable for a long time. This suggests having WeakSubFoos register
themselves with a private static thread that periodically polls its
registrants by invoking getBackingFoo on them but doesn't use the return
value. This can be done once every minute or so and will clear out dead
Foos sooner. Of course the thread has to hold its referents with
WeakReferences and dump references that become null.
Substring copying example with a hypothetical nonfinal MyString:
backerRef = null; q = null;
backer = new MyString(backer.toString().substring(start, end));
end -= start;
start = 0;
Calling methods expect the WeakMySubstring's value to be
getBackingMySubstring from index "start" to index "end", which invariant
is preserved by the above copy-on-parent-doomed routine. Note that this
works also if "end" points one past the end. Note also that the copying
cannot use any WeakMySubstring methods on "this" for fear of an endless
recursion.
method, a Subset class was proposed here recently that would provide a
subset backed by a Set, and a recent mention of image cropping naturally
suggests representing a cropped image by using the source image as a
backer and the added memory consumption is then only the bounds.
The naive implementations (including of String's substring method) tend
to have a problem: the parent object cannot be garbage collected as long
as the child object is floating around free.
I've come up with a fix to that, but it has a cost -- the object has to
be wrapped by one that has a finalizer, which impacts GC performance.
Fortunately the usual need is on larger images, strings, and the like
that are generally long-lived.
* First, don't make the subfoo implementation an inner class of the
backer, or at least only make it a static nested class. A true
inner class instance won't let its parent be garbage collected.
* Next, have the subfoo hold only a weak reference to its parent.
* Now comes the tricky part.
public class WeakSubFoo {
private class Holder {
public Foo foo;
public Holder (Foo foo) { this.foo = foo; }
protected void finalize () {
backerDead(foo);
}
}
private WeakReference<Holder> backer;
private Foo replacement = null;
// whatever else
public WeakSubFoo (Foo backer, other_args) {
this.backer = new WeakReference(new Holder(backer));
// whatever else
}
void backerDead (Foo foo) {
this.replacement = <actually copy relevant part of foo>
// Adjust bounds, e.g. from left=13, top=17, width=32,
// height=32, parentWidth = 1024 to left=0, top=0,
// width=32, height=32, parentWidth = 32 or from
// start=17, end=42 to end -= start, start=0
backer = null;
}
private Foo getBackingFoo () {
Holder h = backer.get();
if (h != null) return h.foo;
while (replacement == null) {
try {
sleep(1000);
} catch (InterruptedException e) {
// Ignore.
}
}
return replacement;
}
// Other logic, using getBackingFoo
}
The major issue I see is that getBackingFoo has to block if it gets
called after the reference is cleared but before the backer is finalized
by the GC. In theory that could be a while. I don't see any easy way to
avoid this, because I don't see any way to actually get a reference to a
weakly-reachable object as soon as it's discovered to be only
weakly-reachable by the GC.
If the backing object is of interface type, this alternative exists:
public final class BackingFoo implements Foo {
private Foo myFoo;
public BackingFoo (Foo foo) { myFoo = foo; }
public Foo getFoo () { return myFoo; }
// Punt implementation of Foo interface to myFoo
}
public class WeakSubFoo {
private WeakReference<BackingFoo> backerRef;
private ReferenceQueue<BackingFoo> q
= new ReferenceQueue<BackingFoo>();
private Foo backer = foo;
// whatever else
public WeakSubFoo (BackingFoo backer, other_args) {
this.backer = backer.getFoo();
this.backerRef
= new WeakReference<BackingFoo>(backer, q);
// whatever else
}
private Foo getBackingFoo () {
if (q.poll() == backerRef) {
backerRef = null;
q = null; // make these reclaimable
backer = <copy of relevant subset of backer>
// Adjust bounds, e.g. from left=13, top=17,
// width=32, height=32, parentWidth = 1024 to
// left=0, top=0, width=32, height=32,
// parentWidth = 32 or from start=17, end=42
// to end -= start, start=0
}
return backer;
}
// Other logic, using getBackingFoo
}
The key to making this work is for client code to use BackingFoo and
WeakSubFoo but never just Foo. All raw Foos should be immediately
wrapped in a BackingFoo. When a BackingFoo with a WeakSubFoo becomes no
longer strongly reachable outside WeakSubFoos, it has in fact become
only weakly reachable. When getBackingFoo eventually is called in a
WeakSubFoo, it finds the reference backerRef on q and responds by
copying the relevant part of backer, which holds the underlying Foo
itself. E.g. a stringalike's 17 to 42 substring to a new stringalike
with only those 26 characters, and the substring position data changed
appropriately. The strong reference to the parent Foo is now gone, and
as long as there are no others (they were held only through references
to the single BackingFoo that is now only weakly reachable), the Foo
itself is now only weakly reachable and is reclaimable.
Besides the need to use exactly one BackingFoo per Foo and avoid holding
direct references to the underlying Foo, there is another caveat: if a
WeakSubFoo goes unused for a long time the backing Foo doesn't become
reclaimable for a long time. This suggests having WeakSubFoos register
themselves with a private static thread that periodically polls its
registrants by invoking getBackingFoo on them but doesn't use the return
value. This can be done once every minute or so and will clear out dead
Foos sooner. Of course the thread has to hold its referents with
WeakReferences and dump references that become null.
Substring copying example with a hypothetical nonfinal MyString:
backerRef = null; q = null;
backer = new MyString(backer.toString().substring(start, end));
end -= start;
start = 0;
Calling methods expect the WeakMySubstring's value to be
getBackingMySubstring from index "start" to index "end", which invariant
is preserved by the above copy-on-parent-doomed routine. Note that this
works also if "end" points one past the end. Note also that the copying
cannot use any WeakMySubstring methods on "this" for fear of an endless
recursion.