Hi all, please see the attached Test8007918Regression.java.

There seems to be a regression caused by this patch: http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/90c9f1577a0b

As the small testcase demonstrates, creating a JPEGImageWriter and calling setOutput causes it to live permanently. This is due to JNI global (non-weak) reference in sun/awt/image/jpeg/imageioJPEG.c.

Is it acceptable to simply make this reference weak ? This should bring object lifetimes back to how they were before this patch. Attached is a patch that, from my tests, fixes the issue.

Thanks & happy hacking,
-Adam
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;

import javax.imageio.ImageWriter;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.MemoryCacheImageOutputStream;

/*
 * Demonstrates an out of memory error using JPEGImageWriter.
 * 
 * This is a regression that seems to be caused by the fix for bug 8007918:
 * http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/90c9f1577a0b
 * 
 * The result of the patch is that JPEGImageWriter will retain itself
 * via a global (non-weak) JNI reference on a call to setOutput(). 
 */
public class Test8007918Regression {

	private static int amountOfJpegImageWritersToCreate = 90000;
	private static int gcGenerationsToRun = 2;
	/* Run GC every 1000 creations*/
	private static int amountBeforeGC = 1000;

	public static ImageWriterSpi getJpegImageWriterSpi() {
		Iterator<ImageWriterSpi> spiIter = IIORegistry.getDefaultInstance().getServiceProviders(ImageWriterSpi.class, true);
		while (spiIter.hasNext()) {
			ImageWriterSpi spi = spiIter.next();
			/* Assumption: JPEG is in the implementation class name for the JPEG image writer SPI */
			String classname = spi.getClass().getSimpleName();
			if (classname.toLowerCase().contains("jpeg")) {
				return spi;
			}
		}
		throw new RuntimeException("No JPEG image writer SPI found.");
	}

	public static void printMemoryUsage() {
		long total = Runtime.getRuntime().totalMemory();
		long free = Runtime.getRuntime().freeMemory();
		System.out.print("TOTAL: " + total/1024 + "Kb");
		System.out.print("\tFREE: " + free/1024 + "Kb");
		System.out.println("\tUSED: " + (total - free)/1024 + "Kb");
	}

    public static void main(String[] args) throws IOException {
		ImageWriterSpi jpegSpi = getJpegImageWriterSpi();

		System.out.println("Before JPEGImageWriter creations: ");
		printMemoryUsage();

		for (int i = 1; i <= amountOfJpegImageWritersToCreate; i++) {
			ImageWriter jpegWriter = jpegSpi.createWriterInstance();
			/* setOutput calls native setDest which seems to create a circular reference via a 
			 * streamBuffer in jdk/src/share/native/sun/awt/image/jpeg/imageioJPEG.c. 
			 * The actual output stream is arbitrary. */
			jpegWriter.setOutput(new MemoryCacheImageOutputStream(new ByteArrayOutputStream()));
			if (i % amountBeforeGC == 0) {
				for (int runs = 0; runs < gcGenerationsToRun; runs++) {
					System.gc();
				}
				System.out.println("After " + i + " JPEGImageWriter creations:");
				printMemoryUsage();
			}
		}
	}
}
diff --git a/src/share/native/sun/awt/image/jpeg/imageioJPEG.c b/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
--- a/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
+++ b/src/share/native/sun/awt/image/jpeg/imageioJPEG.c
@@ -192,7 +192,7 @@ static void unpinStreamBuffer(JNIEnv *en
  */
 static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
     if (sb->stream != NULL) {
-        (*env)->DeleteGlobalRef(env, sb->stream);
+        (*env)->DeleteWeakGlobalRef(env, sb->stream);
         sb->stream = NULL;
     }
     unpinStreamBuffer(env, sb, NULL);
@@ -581,7 +581,7 @@ static void imageio_set_stream(JNIEnv *e
 
     /* Now we need a new global reference for the stream */
     if (stream != NULL) { // Fix for 4411955
-        sb->stream = (*env)->NewGlobalRef(env, stream);
+        sb->stream = (*env)->NewWeakGlobalRef(env, stream);
         if (sb->stream == NULL) {
             JNU_ThrowByName(env,
                             "java/lang/OutOfMemoryError",

Reply via email to