ADDITIONAL SYSTEM INFORMATION :
Windows 10, JDK 8 Update 212
(but the issue is very likely not related to a OS or JDK version)
A DESCRIPTION OF THE PROBLEM :
When URL Class Loader uses a path to a JAR file which is not normalized (e.g. file://any path/../subfolder/some.jar)
And the JAR file contains a META-INF/INDEX.LIST (for optimized resource loading from the JAR)
And any META-INF resource is loaded via the URL Class Loader (e.g. META-INF/spring.schemas)
Then the underlying URL class path implementation opens another input stream to (normalized) JAR path
And closing of the URL Class Loader does not close the additional input streams held in the underlying "loader map (lmap)"
=> LEAK, preventing from unloading/unlocking JAR files
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
for details see description and source code (test case)
When "non-normalized" URL path to JAR file with META-INF/INDEX.LIST is used by URL Class Loader
And any META-INF resource is loaded via the URL Class Loader
And after usage the URL Class Loader is closed
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Then all opened input streams used by the URL Class Loader are closed
And the used/loaded JARs are all unlocked (and can be deleted)
ACTUAL -
Then the URL Class Loader opens additional input stream and keeps it in it's "loader map" (lmap in URL class path)
And those additional input streams (to "normalized" JAR resources ) are NOT closed when unloading/closing the URL Class Loader
And the referenced JAR files a locked on disk and cannot be deleted
---------- BEGIN SOURCE ----------
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Enumeration;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class URLClassLoaderIssueTest {
private final String libDirectory = "lib";
private final String sampleLib = "/xmltooling-1.3.2-1.jar";
private Path workingFolder;
@Before
public void setup() throws IOException {
// we prepare a temp folder with a single JAR file used in our URL class loader to demonstrate the unclosed input stream
TemporaryFolder temporaryFolder = new TemporaryFolder();
temporaryFolder.create();
workingFolder = temporaryFolder.newFolder(libDirectory).toPath();
try (FileOutputStream fileOutputStream = new FileOutputStream(workingFolder + sampleLib)) {
IOUtils.copy(this.getClass().getResourceAsStream(sampleLib), fileOutputStream);
}
}
/**
* Given => a JAR file with META-INF/INDEX.LIST loaded with "normalized" path via the URL class loader
* When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas)
* Then => the URL class loader properly closes all open input streams to loaded resources
*/
@Test
public void loadingDummyResource_withNormalizedJarFilePath_urlClassLoaderClosesInputStream() throws IOException {
// GIVEN
File libFile = new File(workingFolder + sampleLib);
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader());
// WHEN
Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy");
while (resources.hasMoreElements()) {
resources.nextElement();
}
urlClassLoader.close();
// THEN (URL class loader uses normalized path to JAR file, so it can be deleted after closing)
assertTrue(libFile.delete());
}
/**
* Given => a JAR file with META-INF/INDEX.LIST loaded with "non-normalized" path via URL class loader
* When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas)
* Then => the URL class loader keeps unclosed input stream after closing/unloading
*/
@Test
public void loadingDummyResource_withoutNormalizedJarFilePath_urlClassLoaderLeaksInputStream() throws IOException {
// GIVEN
File libFile = new File(workingFolder + "/../" + libDirectory + sampleLib); // without "normalized" path!
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader());
// WHEN
Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy");
while (resources.hasMoreElements()) {
// when URL class loader uses paths to JAR files that are not normalized, the resource loading from the JARs with INDEX.LIST
// opens additional input streams in "loader map (lmap)" of underlying URL class path, which are not considered for closing!
resources.nextElement();
}
urlClassLoader.close();
// THEN (URL class loader does NOT use normalized path to JAR file, so it can NOT be deleted after closing)
assertFalse(libFile.delete());
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
URL paths to JARs with INDEX.LIST passed to the URL Class Loader MUST BE "normalized" in order to avoid opening additional input streams which are not closed any more (leaking)
FREQUENCY : always
Windows 10, JDK 8 Update 212
(but the issue is very likely not related to a OS or JDK version)
A DESCRIPTION OF THE PROBLEM :
When URL Class Loader uses a path to a JAR file which is not normalized (e.g. file://any path/../subfolder/some.jar)
And the JAR file contains a META-INF/INDEX.LIST (for optimized resource loading from the JAR)
And any META-INF resource is loaded via the URL Class Loader (e.g. META-INF/spring.schemas)
Then the underlying URL class path implementation opens another input stream to (normalized) JAR path
And closing of the URL Class Loader does not close the additional input streams held in the underlying "loader map (lmap)"
=> LEAK, preventing from unloading/unlocking JAR files
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
for details see description and source code (test case)
When "non-normalized" URL path to JAR file with META-INF/INDEX.LIST is used by URL Class Loader
And any META-INF resource is loaded via the URL Class Loader
And after usage the URL Class Loader is closed
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Then all opened input streams used by the URL Class Loader are closed
And the used/loaded JARs are all unlocked (and can be deleted)
ACTUAL -
Then the URL Class Loader opens additional input stream and keeps it in it's "loader map" (lmap in URL class path)
And those additional input streams (to "normalized" JAR resources ) are NOT closed when unloading/closing the URL Class Loader
And the referenced JAR files a locked on disk and cannot be deleted
---------- BEGIN SOURCE ----------
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Enumeration;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class URLClassLoaderIssueTest {
private final String libDirectory = "lib";
private final String sampleLib = "/xmltooling-1.3.2-1.jar";
private Path workingFolder;
@Before
public void setup() throws IOException {
// we prepare a temp folder with a single JAR file used in our URL class loader to demonstrate the unclosed input stream
TemporaryFolder temporaryFolder = new TemporaryFolder();
temporaryFolder.create();
workingFolder = temporaryFolder.newFolder(libDirectory).toPath();
try (FileOutputStream fileOutputStream = new FileOutputStream(workingFolder + sampleLib)) {
IOUtils.copy(this.getClass().getResourceAsStream(sampleLib), fileOutputStream);
}
}
/**
* Given => a JAR file with META-INF/INDEX.LIST loaded with "normalized" path via the URL class loader
* When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas)
* Then => the URL class loader properly closes all open input streams to loaded resources
*/
@Test
public void loadingDummyResource_withNormalizedJarFilePath_urlClassLoaderClosesInputStream() throws IOException {
// GIVEN
File libFile = new File(workingFolder + sampleLib);
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader());
// WHEN
Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy");
while (resources.hasMoreElements()) {
resources.nextElement();
}
urlClassLoader.close();
// THEN (URL class loader uses normalized path to JAR file, so it can be deleted after closing)
assertTrue(libFile.delete());
}
/**
* Given => a JAR file with META-INF/INDEX.LIST loaded with "non-normalized" path via URL class loader
* When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas)
* Then => the URL class loader keeps unclosed input stream after closing/unloading
*/
@Test
public void loadingDummyResource_withoutNormalizedJarFilePath_urlClassLoaderLeaksInputStream() throws IOException {
// GIVEN
File libFile = new File(workingFolder + "/../" + libDirectory + sampleLib); // without "normalized" path!
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader());
// WHEN
Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy");
while (resources.hasMoreElements()) {
// when URL class loader uses paths to JAR files that are not normalized, the resource loading from the JARs with INDEX.LIST
// opens additional input streams in "loader map (lmap)" of underlying URL class path, which are not considered for closing!
resources.nextElement();
}
urlClassLoader.close();
// THEN (URL class loader does NOT use normalized path to JAR file, so it can NOT be deleted after closing)
assertFalse(libFile.delete());
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
URL paths to JARs with INDEX.LIST passed to the URL Class Loader MUST BE "normalized" in order to avoid opening additional input streams which are not closed any more (leaking)
FREQUENCY : always
- links to
-
Review
openjdk/jdk/6389