T
Tom Anderson
We recently observed that what seemed like an innocent addition of a
small finalize() method to a class of large objects severely impacted
memory usage. With a finalizer defined, the object's memory can't be
reclaimed as quickly, since it has to stay around until the finalizer
thread can run. Admittedly, this was in a test run where the CPU was
kept arterially busy, but the results were enough to make us find a
different approach.
Interesting. Is your feeling that the problem arose because the objects
were large, rather than numerous? Or because, either way, they were using
a lot of memory?
It would be really nice to have some hard data on this.
I was thinking about this while riding home last night, and it struck me
that you might be able to handle finalizers more efficiently using a write
barrier, as used by a generational garbage collector. You could trap all
writes from the code that runs during finalization, and examine them to
see if they were storing a pointer to the dying objects into the live
heap. If one was, you'd mark the objects live again, and leave them to be
collected later. If no such write occurred, you'd know it was okay to free
the objects there and then, without having to re-scan. There are two ways
to implement write barriers: using OS memory protection functions, and
using the compiler or interpreter to do some special processing on all
writes. The former would probably involve trapping all writes to anywhere
in memory during finalization, since the objects being finalized would
probably be scattered all over memory, and the latter would probably
involve throwing away any compiled code you'd already made and
interpreting or re-compiling the bytecode used during finalization.
Neither is cheap - but both mean that in the usual case, finalizers
wouldn't lead to a big drag on memory use.
Suppose the finalizer tries to allocate memory?
Bad finalizer! I guess there are two options: throw an OOME there and then
in the finalizer, or suspend that finalizer thread and try to finalize and
dispose of some other objects, to free some memory, then return to the
allocating finalizer and finish that one.
tom