
Signature
Unemployed English Java programmer
http://jroller.com/page/tackline/
> With a nasty finaliser, it becomes: the GC nulls the reference, the GC
> finds a finalisable object and puts it on the finaliser queue (might go
> through another queue as well, can't remember), the finaliser thread(s)
> runs the finaliser (may take some time if there's a System.out.println
> in there), the GC finds the finalised object and can reuse the memory.
True, but I don't see how it explains the behaviour that Scott is seeing. If
his example code managed to squeeze out the finaliser thread (as the posted
code does) then that would explain it, but he mentions that other versions of
his code do allow time for the finaliser to run, but the odd behaviour
persists.
Scott said:
> I thought maybe the garbage collection thread couldn't keep up with
> running all of these finalizers, so I tried adding a Thread.sleep(100)
> inside the loop, and that didn't help. Calling System.gc() then
> System.runFinalization() once in each loop didn't help, either; I had
> to call them multiple times.
I have no explanation myself, unless you managed to mess up this part of the
test somehow (I've done that myself /far/ too often to treat it as unlikely, so
please don't be offended).
-- chris
Thomas Hawtin - 16 Dec 2005 16:36 GMT
>>With a nasty finaliser, it becomes: the GC nulls the reference, the GC
>>finds a finalisable object and puts it on the finaliser queue (might go
[quoted text clipped - 7 lines]
> his code do allow time for the finaliser to run, but the odd behaviour
> persists.
Taking a closer look at the code, Scott appears to be refreshing all his
SoftReferences after every allocation. That means the soft-references
will not be cleared right up until the moment an OutOfMemoryError would
be thrown if no more memory can be reclaimed. At that point the
finalisable objects are put on the finaliser queue, where they still
take memory up. No memory is available immediately and hence the JVM
goes OOME.
I have adapted his code to keep counters instead of counting. (Thinking
about it, switching to Reference.isEnqueued should work.) The byte[]
count is impossible, probably due to the (ancient, unclear) bug linked
below. My adaptation has various behaviours depending on parameters
values which determine whether or not there is enough time to run the
finalisers and recover.
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4214755
Tom Hawtin
import java.lang.ref.*;
import java.util.concurrent.atomic.AtomicInteger;
public class SoftRef2 {
final static int NUM_BLOCKS =
Integer.getInteger("NUM_BLOCKS", 256);
final static int BLOCK_SIZE =
Integer.getInteger("BLOCK_SIZE", 1000000);
final static int SLEEP_MILLIS =
Integer.getInteger("SLEEP_MILLIS", 100);
private final static class MemoryHog {
private final byte[] arr;
public MemoryHog(byte[] arr) {
this.arr = arr;
}
public void finalize() {
System.out.println("Finalizing " + this);
}
}
private static class WeakArrayReference
extends WeakReference<byte[]>
{
WeakArrayReference(
byte[] referent, ReferenceQueue<? super byte[]> queue
) {
super(referent, queue);
}
}
public static void main(String[] args) throws Exception {
System.out.println("NUM_BLOCKS: "+NUM_BLOCKS);
System.out.println("BLOCK_SIZE: "+BLOCK_SIZE);
System.out.println("SLEEP_MILLIS: "+SLEEP_MILLIS);
final AtomicInteger arrayCount = new AtomicInteger();
final AtomicInteger hogCount = new AtomicInteger();
final ReferenceQueue<Object> queue =
new ReferenceQueue<Object>();
final Thread thread = new Thread(new Runnable() {
public void run() {
try {
for (;;) {
Reference<?> reference = queue.remove();
if (
reference instanceof WeakArrayReference
) {
arrayCount.decrementAndGet();
} else {
hogCount.decrementAndGet();
}
}
} catch (java.lang.InterruptedException exc) {
// Not expecting that.
exc.printStackTrace();
}
}
}, "Dead counter");
thread.setDaemon(true);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
final java.util.List<Reference<?>> hogPen =
new java.util.ArrayList<Reference<?>>(NUM_BLOCKS);
for(int i=0; i<NUM_BLOCKS; ++i) {
byte[] arr = new byte[BLOCK_SIZE];
new WeakArrayReference(arr, queue);
arrayCount.incrementAndGet();
hogPen.add(new SoftReference<MemoryHog>(
new MemoryHog(arr), queue
));
hogCount.incrementAndGet();
System.out.println(
"Allocated " + (i+1) + "; living: " +
arrayCount.get() + " byte[], "+
hogCount.get() + " head of hog."
);
if (SLEEP_MILLIS != 0) {
Thread.sleep(SLEEP_MILLIS);
}
}
}
}

Signature
Unemployed English Java programmer
http://jroller.com/page/tackline/
Scott W Gifford - 16 Dec 2005 22:17 GMT
[...]
> Taking a closer look at the code, Scott appears to be refreshing all
> his SoftReferences after every allocation.
Yes indeed, thanks for finding that!
[...]
> I have adapted his code to keep counters instead of
> counting. (Thinking about it, switching to Reference.isEnqueued
> should work.) The byte[] count is impossible, probably due to the
> (ancient, unclear) bug linked below. My adaptation has various
> behaviours depending on parameters values which determine whether or
> not there is enough time to run the finalisers and recover.
Do you have a set of parameters that work for you to avoid an OOME?
With the default parameters, I see the same problem with your code as
with mine: the JVM collects nothing until it's almost OOM, then throws
an OOME. I tried increasing SLEEP_MILLIS to 1000, which seems to give
more than ehough time for the finalizer thread to do its work, but
that didn't make a difference at all.
Thanks!
----ScottG.