JDK-6716560 : BufferedImage scaling leaks memory
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 5.0
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2008-06-19
  • Updated: 2014-03-18
jdk 1.5.0_12

Microsoft Windows XP [Version 5.1.2600] SP3

Scaling a BufferedImage instance (by JAI, by g.drawRenderedImage with an AffineTransform, by g.drawImage with g.setScale -- does not matter) consumes an exorbitant amount of memory and does not free that memory.

As a concrete example, supplied in the zip file cited in Steps to Reproduce, a bit map image of size 1750x2250 scaled 1.34x consumes some 40+Mb of memory and does not free the memory after completion of the scaling.

IMO, this is a maximum severity bug as, short of implementing one's own scaling (at a performance hit), there is no way to acceptably handle otherwise seemingly reasonable BI scaling instances. (For example, hoping to scale a 480kb bitmap image like the example image to 4x).

Run the test case class available in this zip with the tiff (also in the zip):

for example:
java bi_memory_one.NoUIZoom -Xmx128m 1750x2250_unc_bitmap.tif 0.67 GRAPHICS_DRAW_SCALE_IMAGE

Scaling a 480kb tiff by 1.34x does not require 55Mb of memory (and if it does for some reason, that memory is free'd after the scaling operation completes).

Memory usage grows like this:
The image loaded should consume about 480Kb of memory (bitsPerPixel * imageWidth * imageHeight).
	Used JVM memory at this point is somewhere around 1.3Mb. Ok.
The first scale to 0.67 the original size is performed. This scaledImage BufferedImage should consume about about 0.21Mb of memory.
	Used JVM memory is somewhere around 14.7Mb now.
The second scale to 1.34 the original size is performed - but not before the scaledImage is disposed of in every conceivable way (and gc() is invoked just to be ridiculous). The new scaledImage should consume about 0.84 megs.
	Used JVM memory is somewhere around 55.7Mb now -- the wings are definitely off the plane.
The third scale is attempted to be made to 2.68x the original size. This should produce a whopping 3.3Mb BufferedImage - but we're out of heap space somehow.

Output from the test case class, using the arguments suggested in the "Steps to Reproduce" dumps:
	Scaling image [C:\Documents and Settings\xxxx\Desktop\1750x2250_unc_bitmap.tif]
		Using scaling type: GRAPHICS_DRAW_SCALE_IMAGE
		total available heap size: 127.06 megs (133234688 bytes)
	--> loaded BI (memory byte size: 0.47 megs (492187 bytes)) [usedMem: 1.29 megs (1351232 bytes)]
		about to start first scaling [usedMem: 1.29 megs (1351232 bytes)] -->
	--> 	buffered image created [usedMem: 1.47 megs (1536752 bytes)]
				about to render scaling by 0.67x [usedMem: 1.47 megs (1540800 bytes)] -->
	--> first scaling finished - byte size of the new image should be 0.21 megs (220775 bytes) [usedMem: 14.67 megs (15380728 bytes)]
		about to start second scaling [usedMem: 14.67 megs (15380728 bytes)] -->
	--> 	buffered image created [usedMem: 1.74 megs (1826840 bytes)]
				about to render scaling by 1.34x [usedMem: 1.74 megs (1826840 bytes)] -->
	--> second scaling finished - byte size of the new image should be 0.84 megs (883771 bytes) [usedMem: 55.7 megs (58405536 bytes)]
		about to start third scaling [usedMem: 55.7 megs (58405536 bytes)] -->
	--> 	buffered image created [usedMem: 5.13 megs (5383480 bytes)]
				about to render scaling by 2.68x [usedMem: 5.13 megs (5383480 bytes)] -->
	Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
		at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41)
		at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2835)
		at bi_memory_one.NoUIZoom.scaleImageViaGraphicsDrawScaleImage(NoUIZoom.java:210)
		at bi_memory_one.NoUIZoom.scaleImage(NoUIZoom.java:120)
		at bi_memory_one.NoUIZoom.runTheTestScenarioAndExit(NoUIZoom.java:294)
		at bi_memory_one.NoUIZoom.main(NoUIZoom.java:362)

This bug can be reproduced always.

---------- BEGIN SOURCE ----------
Class (and tiff, though this happens with all BufferedImage data, regardless of source type) are here:

---------- END SOURCE ----------

Please re-open if - if fix is in progress or on the plan to fix soon - if this is a P3 (file as P3, not P4)

EVALUATION The OOM is because of allocation of intermediate helper image by the draw image pipeline. There is no memory leak here as this intermediate image is freed after completion of transform. To handle generic case intermediate image is of type INT_ARGB and for given example we will be trying to allocate intermediate image with dimensions 4690x6030 (approximately 113Mb) which doesn't fit into the available heap (total heap is 128Mb). Intermediate image is needed for several reasons. E.g. to handle interpolation properly. However, current memory overhead is too high and clearly it should be possible to reduce it for cases like given example. We should investigate ways to bound memory overhead for handling arbitrary transformations (e.g. by using fixed size intermediate buffers) but we also need to make sure it will not hurt performance much.