KT-56789: Fix memory leak in CoreJrtFileSystem

CoreJrtFileSystem uses JrtFileSystemProvider provider to read contents
of jrt-fs from JDK
Implementation of FileSystems.newFileSystem causes metaspace memory leak
that wasn't fixed until JDK 17, see
https://bugs.openjdk.java.net/browse/JDK-8260621

When FileSystems.newFileSystem used to create jrt-fs on JDK9 with
provided java.home value it creates new ClassLoader under-the-hood,
which subsequently leaks due to aforementioned bug

Remove conditional usage of `java.home` + FileSystems.newFileSystem and
switch to use jrt-fs classloader cache regardless of
compiler runtime JDK to reduce classloader leaks

^KT-56789
This commit is contained in:
Simon Ogorodnik
2023-02-22 01:54:02 +02:00
committed by Space Team
parent 0f384f5878
commit 253cdb1b8f
5 changed files with 50 additions and 15 deletions
@@ -34,20 +34,17 @@ class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
val jdkHome = File(jdkHomePath)
val rootUri = URI.create(StandardFileSystems.JRT_PROTOCOL + ":/")
val jrtFsJar = loadJrtFsJar(jdkHome) ?: return@createMap null
val fileSystem =
if (isAtLeastJava9()) {
FileSystems.newFileSystem(rootUri, mapOf("java.home" to jdkHome.absolutePath))
} else {
/*
This ClassLoader actually lives as long as current thread due to ThreadLocal leak in jrtfs,
See https://bugs.openjdk.java.net/browse/JDK-8260621
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
*/
val classLoader = jrtFsClassLoaderCache.computeIfAbsent(jrtFsJar) {
URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
}
FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
}
/*
This ClassLoader actually lives as long as current thread due to ThreadLocal leak in jrt-fs,
See https://bugs.openjdk.java.net/browse/JDK-8260621
So that cache allows us to avoid creating too many classloaders for same JDK and reduce severity of that leak
*/
val classLoader = globalJrtFsClassLoaderCache.computeIfAbsent(jrtFsJar) {
URLClassLoader(arrayOf(jrtFsJar.toURI().toURL()), null)
}
val fileSystem = FileSystems.newFileSystem(rootUri, emptyMap<String, Nothing>(), classLoader)
CoreJrtVirtualFile(this, jdkHomePath, fileSystem.getPath(""), parent = null)
}
@@ -87,6 +84,6 @@ class CoreJrtFileSystem : DeprecatedVirtualFileSystem() {
return Pair(localPath, pathInJar)
}
private val jrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap<File, URLClassLoader>()
private val globalJrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap<File, URLClassLoader>()
}
}