Skip to content

Instantly share code, notes, and snippets.

@swankjesse
Created February 4, 2025 23:56
Show Gist options
  • Save swankjesse/effae2750f11e0b5bf188dfdf12319a9 to your computer and use it in GitHub Desktop.
Save swankjesse/effae2750f11e0b5bf188dfdf12319a9 to your computer and use it in GitHub Desktop.
ClassLoader close bug
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Main {
public static void main(String[] args) throws Exception {
boolean skipClose = args.length > 0 && args[0].equals("skip");
// Build resources.jar, whose file 'hello.txt' contains 'hello world'.
File jarFile = new File("resources.jar");
String resourceName = "hello.txt";
String resourceContent = "hello world";
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile))) {
out.putNextEntry(new ZipEntry(resourceName));
out.write(resourceContent.getBytes(UTF_8));
}
URL[] jarFileUrls = {
jarFile.toURI().toURL()
};
// Create a class loader B and start reading a resource with it.
URLClassLoader loaderB = URLClassLoader.newInstance(jarFileUrls);
InputStream inB = loaderB.getResourceAsStream(resourceName);
// Create class loader A that reads the same jar file.
URLClassLoader loaderA = URLClassLoader.newInstance(jarFileUrls);
try (
InputStream inA = loaderA.getResourceAsStream(resourceName);
BufferedReader readerA = new BufferedReader(new InputStreamReader(inA))
) {
if (readerA.readLine().equals(resourceContent)) {
System.out.println("READ A SUCCESS");
}
}
if (!skipClose) {
loaderA.close();
}
// Finish reading the file with class loader B.
try (BufferedReader readerB = new BufferedReader(new InputStreamReader(inB))) {
if (readerB.readLine().equals(resourceContent)) {
System.out.println("READ B SUCCESS");
}
}
}
}
@swankjesse
Copy link
Author

This crashes attempting to read from inB after loaderA.close().

$ javac Main.java  && java Main
READ A SUCCESS
Exception in thread "main" java.io.IOException: Stream closed
	at java.base/java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:67)
	at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:142)
	at java.base/java.io.FilterInputStream.read(FilterInputStream.java:132)
	at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:270)
	at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:313)
	at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188)
	at java.base/java.io.InputStreamReader.read(InputStreamReader.java:177)
	at java.base/java.io.BufferedReader.fill(BufferedReader.java:162)
	at java.base/java.io.BufferedReader.readLine(BufferedReader.java:329)
	at java.base/java.io.BufferedReader.readLine(BufferedReader.java:396)
	at Main.main(Main.java:48)

@swankjesse
Copy link
Author

If you pass the skip argument to the function to skip closing loaderA, it doesn’t crash:

$ javac Main.java  && java Main skip
READ A SUCCESS
READ B SUCCESS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment