diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CoreJrtFileSystem.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CoreJrtFileSystem.kt index 0a04d9dea21..dbbc4c0a146 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CoreJrtFileSystem.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CoreJrtFileSystem.kt @@ -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(), 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(), classLoader) CoreJrtVirtualFile(this, jdkHomePath, fileSystem.getPath(""), parent = null) } @@ -87,6 +84,6 @@ class CoreJrtFileSystem : DeprecatedVirtualFileSystem() { return Pair(localPath, pathInJar) } - private val jrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap() + private val globalJrtFsClassLoaderCache = ContainerUtil.createConcurrentWeakValueMap() } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonIT.kt index ffe8c400ba0..3e1f0c8e673 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinDaemonIT.kt @@ -136,6 +136,24 @@ class KotlinDaemonIT : KGPDaemonsBaseTest() { } } + @DisplayName("KT-56789: Kotlin daemon does not triggers OOM in Metaspace on multiple invocations") + @JdkVersions(versions = [JavaVersion.VERSION_11]) + @GradleWithJdkTest + @GradleTestVersions(minVersion = TestVersions.Gradle.MAX_SUPPORTED) + fun testMultipleCompilations(gradleVersion: GradleVersion, jdk: JdkVersions.ProvidedJdk) { + project( + "daemonJvmResourceLimits", + gradleVersion, + buildJdk = jdk.location + ) { + for (iteration in 0..300) { + build("clean", "assemble") { + assertKotlinDaemonReusesOnlyOneSession() + } + } + } + } + private fun BuildResult.assertGradleClasspathNotLeaked() { assertOutputContains("Kotlin compiler classpath:") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/build.gradle.kts new file mode 100644 index 00000000000..57cffa3e2cf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies {} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/gradle.properties new file mode 100644 index 00000000000..d9357460e61 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/gradle.properties @@ -0,0 +1 @@ +kotlin.daemon.jvmargs=-XX:MaxMetaspaceSize=128m -Xmx256m \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/src/main/kotlin/helloWorld.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/src/main/kotlin/helloWorld.kt new file mode 100644 index 00000000000..099b13aa0d4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/daemonJvmResourceLimits/src/main/kotlin/helloWorld.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main() { + System.out.println("Hello, world!") + println("Hello, world!") +} \ No newline at end of file