I guess you missed my follow-up message, wherein I describe using a
java.lang.ref.ReferenceQueue to monitor the collection of
BufferedImage instances and the verbose garbage collector output. I
further verify the collection of the BufferedImage instances by
inserting a print statement in the "finalize()" method of their
owner's Class.
Unfortunately, the issue was not as simple as me blundering about
using "top" to monitor memory. Indeed, my use of "top" helped
identify the symptom, but more precise metrics were used to diagnose
the nature of the problem and find a solution.
For clarity, I'll reprint my follow-up:
To confirm that there were no outstanding references to the
BufferedImage instances, I added code to track the objects in
question using a java.lang.ref.ReferenceQueue. By wrapping the
BufferedImages in a WeakReference instance and adding it to my
ReferenceQueue, I was able to receive notifications from the
garbage collector (GC) when the objects are being prepared for
collection. What was interesting is that each and every
BufferedImage instance was being scheduled for collection (as
confirmed by my ReferenceQueue). However, the memory associated
with them was not being reclaimed, as verbose output from the GC
showed the heap growing steadily over time.
As a final test, I put some print statements in the "finalize()"
method of the Class that owns the BufferedImages. From this
output, I was able to confirm that these objects were being finalized.
Late yesterday I did some research that suggested that the
Image.flush() method might help, and when I added this call to the
"finalize()" method of the owning Class, things improved
dramatically. To confirm that the memory was being reclaimed, I
used the "-Xmx" command line argument to vary the size of Java's
heap. By limiting its size, I was able to force more frequent full
garbage collections to confirm that the memory was being reclaimed.
So thanks for your suggestion -- it turns out the reason why the
BufferedImage objects were being collected (as I confirmed) but
their memory was not reclaimed was because their "flush()" method
was not explicitly invoked.
Hope this sheds some light on things.
Regards,
-Mike Ellis
On Mar 29, 2006, at 5:22 AM, email@hidden wrote:
Russ Calvert wrote:
I've just checked the source for BufferedImage, and flush() seems
to be an
empty method - not even a call to super.flush(). That seems rather
odd to
me.
The superclass of BufferedImage is java.awt.Image, which has an
abstract
flush(). No purpose would be served by calling super.flush(). I'm
not
even sure it would compile. Doing so would specifically ask to
invoke an
abstract non-virtual method, which seems slightly illegal to me,
but may
nonetheless be allowed; try it and see.
The 1.4 source I have does show a body for BufferedImage.flush():
public void flush() {
// MACOSX
if (this.sData != null)
{
sData.finishLazyDrawing();
}
}
Apparently, the implementation of BufferedImage.flush() may vary.
I detect the scent of wild goose in the air. I'm not at all
convinced that
flush() will discard the underlying Raster or its DataBuffer or any of
their internal arrays. The comment for flush() says nothing that
leads me
to believe it's like a dispose() method (e.g. Window.dispose()) or
like a
finalizer that's releasing an underlying resource. Even if there were
underlying resources that have to be released, I would expect to
find them
in the Raster or the DataBuffer, not in the BufferedImage itself.
If there is an actual memory leak related to BufferedImage, I'm more
inclined to suspect the known bug about direct-buffers not being
properly
GC'ed. But I'd only suspect that if the Raster or DataBuffer
actually used
a direct-buffer or some other form of releasable underlying
resource. If
everything is done with straight int[]'s and so on, then the GC'er
itself
will release the objects and arrays when they become unreachable,
and no
finalizer or dispose() or flush() needs to do anything else about
releasing
those objects. That's how Java's GC is designed to work.
Referring back to the original post, the poster specifically said
that he
was observing memory usage with 'top'. That approach is guaranteed
to NOT
show any releases of BufferedImage, because 'top' is incapable of
looking
into the Java heap. It's all just one opaque blob of memory as far as
'top' is concerned.
So the short answer to the original post is that the observed
behavior with
'top' is exactly as expected, and it has nothing to do with whether
there
is or is not a leak with BufferedImages. Or more precisely, if
there is a
leak, it's probably because a direct-buffer or other resource isn't
being
properly released by GC, and that may be only a consequence of a
direct-buffer bug, and isn't directly related to BufferedImages.
If the
BufferedImage is a pure Java object, though, then 'top' can't
possibly show
any leaks, except indirectly as the heap grows until it reaches its
upper
limit, then is slowly exhausted.
To really find out the state of the Java heap, you have to inspect the
state of the Java heap, and 'top' can't possibly do that. Verbose-
GC can
do it, and so can 1.5's jconsole or another JMX tool that specifically
looks at the Java heap.
If some varieties of BufferedImages are being backed by direct-
buffers, and
if there is still a problem with direct-buffers or other resources not
being properly released by GC, then I don't know if even jconsole
would
tell you much.
Native resources, e.g. a screen-memory raster-image, often have small
representations in the Java heap, but are tied to larger resources
elsewhere. This is intentional: a simple proxy funnels access to
and from
some other thing. The proxy object itself is small, with a small
memory
cost, but it represents some resource with a much larger actual
cost. I
don't think "cost" or "size" is reflected in the Java object
itself, so GC
has no way to know whether it's approaching exhaustion of the proxied
resource or not.
It's basically the same problem as open file-descriptors (file-
handles).
GC doesn't know the per-object cost, nor does it know what the
upper limit
of the resource itself is. The OS knows, of course, but GC is
ignorant of
those resources, their costs, their limits, what it should do to
release or
reclaim them, or even WHEN it should try to reclaim them as exhaustion
approaches. All GC can measure or act on is the heap memory cost
of an
object. But when the heap-object itself is small, the GC'er can't
correctly gauge the cost of NOT doing a full GC that eventually
reclaims
every unreachable object.
-- GG
=========================
Michael F. Ellis
President
Ellis Softworks Inc.
----------
Phone: (941) 713-0361
Email: email@hidden
Web: http://www.ellissoftworks.com
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Java-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/java-dev/email@hidden