Mailing Lists: Apple Mailing Lists

Image of Mac OS face in stamp
 
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: BufferedImages not garbage collected -- any suggestions?




Hi Greg.

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

This email sent to email@hidden


Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Contact Apple | Terms of Use | Privacy Policy

Copyright © 2007 Apple Inc. All rights reserved.