Skip to main content

Garbage collecting a BufferedImage

17 replies [Last post]
Anonymous

Hello,

I am having problems reclaiming memory consumed by a (supposedly
GC'ed) BufferedImage. When I want to dispose of an image, I basically
do:

img.flush();
img = null;

I was expecting this to free the memory from all resources associated
with this BufferedImage. Unfortunately it does not seem to be the
case. After some time I get a java.lang.OutOfMemoryError: Java heap
space. Manual calls to System.gc() do not change anything to the
current memory consumption. I ran tests with a pretty big heap size (-
Xmx1024M). I monitored the memory consumption using Runtime.getRuntime
().totalMemory() and Runtime.getRuntime().freeMemory().

Am I missing something?

Some more details:

This problem occurs both when I create a BufferedImage through
javax.imageio.ImageIO.read(), and when I create one directly with a
BufferedImage constructor. I noticed it while writing a simple
slideshow application which I tested with pretty big images
(2048x1536 on average). At first I thought this was a bug specific to
the Mac OS X JVM: on one hand, if I load the images using
javax.swing.ImageIcon, everything is fine because I get an instance
of apple.awt.OSXImage, which seems to be correctly GC'ed. But on the
other hand, if I load the image with ImageIO.read(), I get a
BufferedImage, which is not GC'ed when I "kill" it as described above.

Then I tried on Windows XP with Java 1.6.0_02, and the same behavior
occurs: instances of sun.awt.image.ToolkitImage are correctly GC'ed,
whereas BufferedImage instances are not.

I looked on Google, on Sun's bug database, and on Apple's java-dev
mailing list. The only thing I could find was this thread [1], but
which apparently does not give an answer to the problem.

[1] http://lists.apple.com/archives/Java-dev/2006/Mar/msg00372.html

Thanks,
Emmanuel

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
jwenting
Offline
Joined: 2003-12-02

> I am having problems reclaiming memory consumed by a
> (supposedly
> GC'ed) BufferedImage. When I want to dispose of an
> image, I basically
> do:
>

That's because in Java you never explicitly reclaim memory.

> img.flush();
> img = null;
>

Nice, but rather pointless.

> I was expecting this to free the memory from all

Wrong expectations.

> resources associated
> with this BufferedImage. Unfortunately it does not
> seem to be the

As you should know if you'd known a bit more about the Java memory model and programming paradigm.

> case. After some time I get a
> java.lang.OutOfMemoryError: Java heap

Could happen.

> space. Manual calls to System.gc() do not change
> anything to the

Of course not. As you would know if you knew more about Java memory management.

> current memory consumption. I ran tests with a pretty
> big heap size (-

which only delays the inevitable

> Xmx1024M). I monitored the memory consumption using
> Runtime.getRuntime
> ().totalMemory() and
> Runtime.getRuntime().freeMemory().
>
> Am I missing something?
>

yes, you're hanging on to references to that BufferedImage somewhere, preventing it from being garbage collected.
So you're getting rid of some references to that image, but never the image itself.

Did you put it in a Collection somewhere, maybe?

> BufferedImage constructor. I noticed it while writing
> a simple
> slideshow application which I tested with pretty big
> images

So you're creating a List of BufferedImage instances for later display?
If so, there's the problem.
Each image is using several megabytes of memory and you're hanging on to all of them.
Sooner or later you're bound to run out.

> BufferedImage, which is not GC'ed when I "kill" it as
> described above.
>
You don't "kill" anything. You only null a reference, but apparently not all of them.

flar
Offline
Joined: 2003-06-11

While the attempts to free the Java array memory associated with a BufferedImage with explicit method calls were in vain, I wanted to counter some unnecessarily general comments here.

> > I am having problems reclaiming memory consumed by
> a
> > (supposedly
> > GC'ed) BufferedImage. When I want to dispose of an
> > image, I basically
> > do:
> >
>
> That's because in Java you never explicitly reclaim
> memory.

This is true for Java heap memory (memory allocated with the new operator), but not for other platform and C heap memory allocated by the Java libraries to support some operations on Java objects. For the most part we try to keep such non-Java-heap memory usage to a minimum, but in the case of images we can use non-Java-heap memory in a few places. In most, if not all, cases where the libraries use platform memory to facilitate the Java object operations there is usually an explicit dispose() method call to free the underlying resources aggressively and it is typically backed up with some form of finalization to make sure the resources are reclaimed even if regular garbage collection is relied upon.

But, the point that I wanted to make is that there are some cases where the developer can explicitly participate in resource reclamation even in a fully garbage-collected environment.

> > img.flush();
> > img = null;
>
> Nice, but rather pointless.

These are not pointless for VolatileImage, Toolkit, and some BufferedImage images as I pointed out in a prior post. This call can and does free some system resources, notably VRAM resources.

It will not, however, free the primary Java array associated with a BufferedImage which was the original intent of the thread.

flar
Offline
Joined: 2003-06-11

One thing to note - img.flush() doesn't "dispose" or "free" the image - it only gets rid of cached resources. So the things it gets rid of are:

- For a Toolkit image, it gets rid of the in-memory representation, which can be rebuilt by reloading the image from the source if you use it again.

- For a VolatileImage, it gets rid of the VRAM, but you can later get the VRAM back (with uninitialized contents) if you validate it as you might in a rendering loop.

- For a BufferedImage, it gets rid of any cached *copies* of the image in VRAM which we create if you copy it to an accelerated destination a few times so that future copies go faster. It does not get rid of the original bits because that is the only source of the image (BI are not typically used in a "validate/restore" loop like a VI, and have no source like a TI).

I believe the ImageIcons use TI internally by default so you can flush that image and it will get rid of the in-core version. But, if you use a BI then flush() does nothing (for the Java heap/core memory). BI are only dealt with via Garbage Collection.

My guess is that you have an outstanding reference to the image stored somewhere that prevents GC and the flush() simply works around the leaked reference. Can you write a small reproducible test case (like 20 or 30 lines of code) that demonstrates the same leak? If not, then maybe there is a leaked reference lost somewhere in the complexity of your full application...?

cobrien
Offline
Joined: 2004-06-09

Do you draw anything on the images? I seem to remember having a similar
problem and I think I got the Graphics Context from the image to draw on it,
and had a problem until I added gc.dispose(). I'm not sure, I can't seem to
locate the code.

Emmanuel Pietriga

On 21 nov. 07, at 15:14, java2d@JAVADESKTOP.ORG wrote:

> Do you draw anything on the images? I seem to remember having a
> similar
> problem and I think I got the Graphics Context from the image to
> draw on it,
> and had a problem until I added gc.dispose(). I'm not sure, I
> can't seem to
> locate the code.
> [Message sent by forum member 'cobrien' (cobrien)]

No, I just draw that image with a call to Graphics2D.drawImage().
There are calls to Graphics2D.setTransform() and
Graphics2D.setAlphaComposite() before the call to drawImage, but
that's all.

Emmanuel

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

Just to be curious: Does the problem also happen if you call setAccelerationPriority(0) on the buffered image right after construction?

lg Clemens

Emmanuel Pietriga

On 21 nov. 07, at 12:56, java2d@JAVADESKTOP.ORG wrote:

> Just to be curious: Does the problem also happen if you call
> setAccelerationPriority(0) on the buffered image right after
> construction?
>
> lg Clemens

Yes, it does happen. No difference. I tried both on my Mac and on the
Windows machine (which is running 1.6.0_03 BTW, not _02).

Emmanuel

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

Well you could try to create a heap-snapshot using jconsole (MBeans->com.sun.management->Hotspot-diagnostics->dumpHeap)
and later view it using jhat.
You should be able to follow the references to the BufferedImages.

For the first time it sounds complicated, but those free tools are a great help for everyday's problems.

lg Clemens

Emmanuel Pietriga

On 21 nov. 07, at 14:25, java2d@JAVADESKTOP.ORG wrote:

> Well you could try to create a heap-snapshot using jconsole (MBeans-
> >com.sun.management->Hotspot-diagnostics->dumpHeap)
> and later view it using jhat.
> You should be able to follow the references to the BufferedImages.
>
> For the first time it sounds complicated, but those free tools are
> a great help for everyday's problems.
>
> lg Clemens

Eventually that is probably what I will do. But I was wondering if I
was missing something obvious to actually dispose of all resources
associated with the BufferedImage (because my code does work fine
with an OSXImage or an awt.ToolkitImage).

Thanks for the tip,
Emmanuel

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

> Eventually that is probably what I will do. But I was
> wondering if I
> was missing something obvious to actually dispose of
> all resources
> associated with the BufferedImage

By default you don't need to care about BufferedImages - if you don't hold any references it up to the GC to free java-related memory, and up to the "Java2D Disposer" to clean up native resources associated with it (that was why I ask wether seting a different accaleration-priority would help).

However because BufferedImages tend to be that widely used the chance you've hit a bug in java is quite small. If you could post the code (or even the compiled application), I could have a look at it.

lg Clemens

Emmanuel Pietriga

On 21 nov. 07, at 15:36, java2d@JAVADESKTOP.ORG wrote:

>> Eventually that is probably what I will do. But I was
>> wondering if I
>> was missing something obvious to actually dispose of
>> all resources
>> associated with the BufferedImage
>
> By default you don't need to care about BufferedImages - if you
> don't hold any references it up to the GC to free java-related
> memory, and up to the "Java2D Disposer" to clean up native
> resources associated with it (that was why I ask wether seting a
> different accaleration-priority would help).

Yes indeed.

> However because BufferedImages tend to be that widely used the
> chance you've hit a bug in java is quite small. If you could post
> the code (or even the compiled application), I could have a look at
> it.
>
> lg Clemens
> [Message sent by forum member 'linuxhippy' (linuxhippy)]

I've put the 2 JAR files at [1,2]. Here is the associated command line:
java -jar zslideshow-0.1.0-SNAPSHOT.jar

I'm not sure I am doing anything "wrong", though, as the app works
perfectly fine when I instantiate images as OSXImage or
awt.ToolkitImage. And the code is exactly the same except for the
line where I instantiate the java.awt.Image (either using ImageIcon
in which case I get an OSXImage, or ImageIO.read() in which case I
get a BufferedImage).

One notable thing: my call to Image.flush() does not occur on the
EDT, but in a thread created by my own app (not the main thread). I
do not see why this should be a problem, but I just thought I'd
mention it.

The source code is kind of large, as it uses my own Java2D-based ZUI
(zoomable user interface [3]) toolkit to actually display the images.
If you really want to have a look at it, [4] is where images get
created and destroyed. [5] is the class encapsulating the image and
containing the actual drawing code.

[1] http://www.lri.fr/~pietriga/2007/11/zslideshow-0.1.0-SNAPSHOT.jar
[2] http://www.lri.fr/~pietriga/2007/11/zvtm-0.9.6-SNAPSHOT.jar
[3] http://zvtm.sourceforge.net/
[4] http://zvtm.svn.sourceforge.net/viewvc/zvtm/zslideshow/trunk/src/
main/java/net/claribole/zslideshow/ImageDescription.java?view=markup
[5] http://zvtm.svn.sourceforge.net/viewvc/zvtm/zvtm/trunk/src/main/
java/net/claribole/zvtm/glyphs/VImageST.java?view=markup

thanks,
Emmanuel

--
Emmanuel Pietriga
INRIA Futurs - Projet In Situ tel : +33 1 69 15 34 66
Bat 490, Université Paris-Sud fax : +33 1 69 15 65 86
91405 ORSAY Cedex FRANCE http://www.lri.fr/~pietriga

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

> I've put the 2 JAR files at [1,2]. Here is the
> associated command line:
> java -jar zslideshow-0.1.0-SNAPSHOT.jar

I tried it but the only thing I can see is a colored bar showing the heap-useage on Linux (which remains constant). I guess I am using it in the wrong way, or are maybe some images missing?

I'll have a look at it as soon as I can get it up&running.

lg Clemens

Emmanuel Pietriga

On 21 nov. 07, at 20:12, java2d@JAVADESKTOP.ORG wrote:

>> I've put the 2 JAR files at [1,2]. Here is the
>> associated command line:
>> java -jar zslideshow-0.1.0-SNAPSHOT.jar
>
> I tried it but the only thing I can see is a colored bar showing
> the heap-useage on Linux (which remains constant). I guess I am
> using it in the wrong way, or are maybe some images missing?
>
> I'll have a look at it as soon as I can get it up&running.
>

Sorry, I knew I had forgotten something. Click anywhere in that
window to give it focus, then press "o". This will pop-up a
JFileChooser. Select a directory that contains JPEG and/or PNG images
(preferably large ones). You will then be able to look at the images
in that directory one by one using the left and right arrow keys.

Emmanuel

> lg Clemens
> [Message sent by forum member 'linuxhippy' (linuxhippy)]
>
> http://forums.java.net/jive/thread.jspa?messageID=246639
>
> ======================================================================
> =====
> To unsubscribe, send email to listserv@java.sun.com and include in
> the body
> of the message "signoff JAVA2D-INTEREST". For general help, send
> email to
> listserv@java.sun.com and include in the body of the message "help".

--
Emmanuel Pietriga
INRIA Futurs - Projet In Situ tel : +33 1 69 15 34 66
Bat 490, Université Paris-Sud fax : +33 1 69 15 65 86
91405 ORSAY Cedex FRANCE http://www.lri.fr/~pietriga

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

Hi Emanuel,

It seems your application still holds references to that image:

java.awt.image.BufferedImage@0x5b42b2d0 (37 bytes) :
net.claribole.zvtm.glyphs.VImageST@0x5b42aaa0 (165 bytes) :
java.util.Hashtable$Entry@0x5b42a5e8 (24 bytes)
[Ljava.util.Hashtable$Entry;@0x6a6a77b8 (1540 bytes)
java.util.Hashtable@0x5511c128 (40 bytes)
com.xerox.VTM.engine.VirtualSpaceManager@0x551193a8 (91 bytes) : field allGlyphs

That or similar reference-chains I found using jhat easily. It seems VirtualSpaceManager stores the VImageST references even if you remove them.

Good luck with your opensource project, lg Clemens

Emmanuel Pietriga

On 21 nov. 07, at 21:09, java2d@JAVADESKTOP.ORG wrote:

> Hi Emanuel,
>
> It seems your application still holds references to that image:
>
> java.awt.image.BufferedImage@0x5b42b2d0 (37 bytes) :
> net.claribole.zvtm.glyphs.VImageST@0x5b42aaa0 (165 bytes) :
> java.util.Hashtable$Entry@0x5b42a5e8 (24 bytes)
> [Ljava.util.Hashtable$Entry;@0x6a6a77b8 (1540 bytes)
> java.util.Hashtable@0x5511c128 (40 bytes)
> com.xerox.VTM.engine.VirtualSpaceManager@0x551193a8 (91 bytes) :
> field allGlyphs
>
> That or similar reference-chains I found using jhat easily. It
> seems VirtualSpaceManager stores the VImageST references even if
> you remove them.
>
> Good luck with your opensource project, lg Clemens
> [Message sent by forum member 'linuxhippy' (linuxhippy)

Hello all,

Clemens, you were right... I am ashamed. I thought I was cleaning
that reference, while I was actually not. I guess I over-sought this
possibility simply because everything was working fine with OSXImage
or awt.ToolkitImage (even though the reference was not cleaned up).

Thanks a lot and my apologies for the trouble,
Emmanuel

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

linuxhippy
Offline
Joined: 2004-01-07

> Clemens, you were right... I am ashamed. I thought I
> was cleaning
> that reference, while I was actually not. I guess I
> over-sought this
> possibility simply because everything was working
> fine with OSXImage
> or awt.ToolkitImage (even though the reference was
> not cleaned up).
>
> Thanks a lot and my apologies for the trouble,

No problem at all, however I can really recommend using those tools:
- jconsole: To moonitor jvm behaviour (gc-workload, ...., generate heap snapshots)
- jhat: To inspect heap-snapshots (can be very helpful to find leaks and other memory hungry stuff)
- Netbeans profiler: Run it every now and then and see whats going on. It even helped me often to find hidden bugs where my code hit a slow-path even for very common operations just because I forgot to write a fast-path ^^

lg Clemens

Dmitri Trembovetski

Thanks a lot, Clemens, for looking into this!

Dmitri

java2d@JAVADESKTOP.ORG wrote:
>> Clemens, you were right... I am ashamed. I thought I
>> was cleaning
>> that reference, while I was actually not. I guess I
>> over-sought this
>> possibility simply because everything was working
>> fine with OSXImage
>> or awt.ToolkitImage (even though the reference was
>> not cleaned up).
>>
>> Thanks a lot and my apologies for the trouble,
>
> No problem at all, however I can really recommend using those tools:
> - jconsole: To moonitor jvm behaviour (gc-workload, ...., generate heap snapshots)
> - jhat: To inspect heap-snapshots (can be very helpful to find leaks and other memory hungry stuff)
> - Netbeans profiler: Run it every now and then and see whats going on. It even helped me often to find hidden bugs where my code hit a slow-path even for very common operations just because I forgot to write a fast-path ^^
>
> lg Clemens
> [Message sent by forum member 'linuxhippy' (linuxhippy)]
>
> http://forums.java.net/jive/thread.jspa?messageID=246742
>
> ===========================================================================
> To unsubscribe, send email to listserv@java.sun.com and include in the body
> of the message "signoff JAVA2D-INTEREST". For general help, send email to
> listserv@java.sun.com and include in the body of the message "help".

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".