Kotlin JVM args: Inheritance & Defaults

A story of verifying JVM settings and why writing clear documentation is important.

Kotlin JVM args: Inheritance & Defaults
Powering up Kotlin with JVM args

This is a post in my JVM Args for Builds series where I break down how these settings affect JVM-based build systems. A number of these settings are complicated and all have compounding effects on each other as the JVM is a complex machine, but used the right way can be the difference between having a high-value feedback build system versus a high-cost one.

One of Kotlin's main targets is the JVM and therefore how it sets up its daemon for compilation can be tweaked via its JVM args. Over the years we've gotten better hooks and documentation that make it easier to set them correctly, especially with the introduction of the kotlin.daemon.jvmargs property. Today's article is going to cover the inheritance and defaults of JVM args the Kotlin daemon runs with.


TLDR;

For the vast majority of JVM build systems that are small-medium projects this doesn't impact you. You might be running settings you didn't expect, but they don't matter significantly.

For medium to large projects that have tried to apply JVM arg best practices you might have assumed the Kotlin daemon is copying all JVM args from Gradle JVM args because the Kotlin documentation was a bit vague. You should check out your settings and verify via VisualVM. Since I started writing this I fixed Kotlin's documentation on the topic, but that doesn't fix all the build systems out there. Its up to you to verify and adjust accordingly.


Unexpected Behavior

I was checking over some JVM profiling I had done for a build. One of the suggestions I got along the way was to remove all the Kotlin JVM args because they were all the same, and that made sense so I did it. But then I went to check out the results in profiling and saw slightly different numbers. That's odd. So I looked up how I could verify the JVM args in a running process and came across the tool VisualVM which does that and more.

After downloading it VisualVM wasn't picking up my JAVA_HOME so I opened it via Terminal with the manual flag:

/Applications/VisualVM.app/Contents/MacOS/visualvm --jdkhome $JAVA_HOME

I'd been setting the following JVM args:

org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=1 -XX:+HeapDumpOnOutOfMemoryError -Xmx3g -Xms3g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g

gradle.properties file in NowInAndroid

The Gradle Daemon looks okay...

But the Kotlin Daemon has a bunch of stuff in it I didn't specify and missing most of the Gradle JVM args!

Okay, so there must be some filtering going on somewhere in the Kotlin codebase that contains both the Kotlin Gradle Plugin and Kotlin Daemon for JVM. For the benefit of the curious I documented that journey, otherwise you can skip the entire next section to see what I found.


Down the rabbit hole

The Gradle property kotlin.daemon.jvmargs is read by KGP in PropertiesProvider.kt [[4]]

package org.jetbrains.kotlin.gradle.plugin

internal class PropertiesProvider private constructor(private val project: Project) {

    val kotlinDaemonJvmArgs: String?
        get() = property("kotlin.daemon.jvmargs").orNull

kotlinDaemonJvmArgs gets used in AbstractKotlinCompileConfig.kt [[5]]

package org.jetbrains.kotlin.gradle.tasks.configuration

/**
 * Configuration for the base compile task, [org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile].
 *
 * This contains all data necessary to configure the tasks, and should avoid exposing global state (project, extensions, other tasks)
 * to the task instance as much as possible.
 */
internal abstract class AbstractKotlinCompileConfig<TASK : AbstractKotlinCompile<*>>(
    project: Project,
    val ext: KotlinTopLevelExtension
) : TaskConfigAction<TASK>(project) {

    init {
        val compilerSystemPropertiesService = CompilerSystemPropertiesService.registerIfAbsent(project)
        val buildFinishedListenerService = BuildFinishedListenerService.registerIfAbsent(project)
        val buildIdService = BuildIdService.registerIfAbsent(project)
        configureTask { task ->
            val propertiesProvider = project.kotlinPropertiesProvider

            propertiesProvider.kotlinDaemonJvmArgs?.let { kotlinDaemonJvmArgs ->
                task.kotlinDaemonJvmArguments.set(providers.provider {
                    kotlinDaemonJvmArgs.split("\\s+".toRegex())
                })
            }

kotlinDaemonJvmArguments gets used in CompileUsingKotlinDaemonWithNormalization.kt [[6]]

package org.jetbrains.kotlin.gradle.tasks

internal interface CompileUsingKotlinDaemonWithNormalization : CompileUsingKotlinDaemon {

    @get:Internal
    val normalizedKotlinDaemonJvmArguments: Provider<List<String>>
        get() = kotlinDaemonJvmArguments.map {
            it.map { arg -> arg.trim().removePrefix("-") }
        }
}

normalizedKotlinDaemonJvmArguments gets used in AbstractKotlinCompile.kt [[7]]

package org.jetbrains.kotlin.gradle.tasks

@DisableCachingByDefault(because = "Abstract super-class, not to be instantiated directly")
abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constructor(
    objectFactory: ObjectFactory,
    workerExecutor: WorkerExecutor,
) : AbstractKotlinCompileTool<T>(objectFactory),
    CompileUsingKotlinDaemonWithNormalization,
    UsesBuildMetricsService,
    UsesIncrementalModuleInfoBuildService,
    UsesCompilerSystemPropertiesService,
    UsesVariantImplementationFactories,
    UsesBuildFinishedListenerService,
    UsesClassLoadersCachingBuildService,
    UsesKotlinToolingDiagnostics,
    UsesBuildIdProviderService,
    UsesBuildFusService,
    BaseKotlinCompile {

    @get:Internal
    internal val compilerRunner: Provider<GradleCompilerRunner> =
        objectFactory.propertyWithConvention(
            gradleCompileTaskProvider.flatMap { taskProvider ->
                compilerExecutionStrategy
                    .zip(metrics) { executionStrategy, metrics ->
                        metrics to executionStrategy
                    }
                    .flatMap { params ->
                        defaultKotlinJavaToolchain
                            .map {
                                val toolsJar = it.currentJvmJdkToolsJar.orNull
                                createGradleCompilerRunner(
                                    taskProvider,
                                    toolsJar,
                                    CompilerExecutionSettings(
                                        normalizedKotlinDaemonJvmArguments.orNull,
                                        params.second,
                                        useDaemonFallbackStrategy.get()
                                    ),
                                    params.first,
                                    workerExecutor,
                                    runViaBuildToolsApi.get(),
                                    classLoadersCachingService,
                                    buildFinishedListenerService,
                                    buildIdService,
                                    buildFusService.orNull?.getFusMetricsConsumer()
                                )
                            }
                    }
            }
        )

normalizedKotlinDaemonJvmArguments gets passed into CompilerExecutionSettings.kt [[8]]

package org.jetbrains.kotlin.compilerRunner

import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
import java.io.Serializable

internal data class CompilerExecutionSettings(
    val daemonJvmArgs: List<String>?,
    val strategy: KotlinCompilerExecutionStrategy,
    val useDaemonFallbackStrategy: Boolean,
) : Serializable {
    companion object {
        const val serialVersionUID: Long = 0
    }
}

This is a data class, so an instance of CompilerExecutionSettings must be created. Its field daemonJvmArgs is then used by GradleKotlinCompilerWork.kt [[9]]

package org.jetbrains.kotlin.compilerRunner

internal class GradleKotlinCompilerWork @Inject constructor(
    private val config: GradleKotlinCompilerWorkArguments
) : Runnable {

    private fun compileWithDaemon(messageCollector: MessageCollector): ExitCode {
        val isDebugEnabled = log.isDebugEnabled || System.getProperty("kotlin.daemon.debug.log")?.toBoolean() ?: true
        val daemonMessageCollector =
            if (isDebugEnabled) messageCollector else MessageCollector.NONE
        val connection =
            metrics.measure(GradleBuildTime.CONNECT_TO_DAEMON) {
                GradleCompilerRunner.getDaemonConnectionImpl(
                    config.projectFiles.clientIsAliveFlagFile,
                    config.projectFiles.sessionFlagFile,
                    config.compilerFullClasspath,
                    daemonMessageCollector,
                    isDebugEnabled = isDebugEnabled,
                    daemonJvmArgs = config.compilerExecutionSettings.daemonJvmArgs
                )

Then we find getDaemonConnectionImpl in GradleKotlinCompilerRunner.kt [[10]]

package org.jetbrains.kotlin.compilerRunner

/*
Using real taskProvider cause "field 'taskProvider' from type 'org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner':
value 'fixed(class org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated, task ':compileKotlin')'
is not assignable to 'org.gradle.api.tasks.TaskProvider'" exception
 */
internal open class GradleCompilerRunner(
    protected val taskProvider: GradleCompileTaskProvider,
    protected val jdkToolsJar: File?,
    protected val compilerExecutionSettings: CompilerExecutionSettings,
    protected val buildMetrics: BuildMetricsReporter<GradleBuildTime, GradleBuildPerformanceMetric>,
    protected val fusMetricsConsumer: StatisticsValuesConsumer?,
) {


    companion object {
        @Synchronized
        internal fun getDaemonConnectionImpl(
            clientIsAliveFlagFile: File,
            sessionIsAliveFlagFile: File,
            compilerFullClasspath: List<File>,
            messageCollector: MessageCollector,
            daemonJvmArgs: List<String>?,
            isDebugEnabled: Boolean,
        ): CompileServiceSession? {
            val compilerId = CompilerId.makeCompilerId(compilerFullClasspath)
            val daemonJvmOptions = configureDaemonJVMOptions(
                inheritMemoryLimits = true,
                inheritOtherJvmOptions = false,
                inheritAdditionalProperties = true
            ).also { opts ->
                if (!daemonJvmArgs.isNullOrEmpty()) {
                    opts.jvmParams.addAll(
                        daemonJvmArgs.filterExtractProps(opts.mappers, "", opts.restMapper)
                    )
                }
            }

Okay now we're getting to the juicy bit where we are going to configure Kotlin JVM options to inherit Gradle or JVM memory limits and options.

configureDaemonJVMOptions(
    inheritMemoryLimits = true,
    inheritOtherJvmOptions = false,
    inheritAdditionalProperties = true
)

The above is an top level function in DaemonParams.kt [[11]]. There's also the DaemonJVMOptions data class which is used to inherit JVM options, but only maxMemory, maxMetaspaceSize, and reservedCodeCacheSize. Now it makes perfect sense why I was seeing the behavior I was - but another surprise is the default value of 320m for -XX:ReservedCodeCacheSize. This seems fine to me as I've been setting this value to 256m - 512m in my builds anyway, but I'll write an analysis on CodeCache JVM args another day. The rest of this code also explains a few of the other JVM args being added like -ea, but not all of them.

package org.jetbrains.kotlin.daemon.common

data class DaemonJVMOptions(
        var maxMemory: String = "",
        var maxMetaspaceSize: String = "",
        var reservedCodeCacheSize: String = "320m",
        var jvmParams: MutableCollection<String> = arrayListOf()
) : OptionsGroup {
    override val mappers: List<PropMapper<*, *, *>>
        get() = listOf(StringPropMapper(this, DaemonJVMOptions::maxMemory, listOf("Xmx"), mergeDelimiter = ""),
                       StringPropMapper(this, DaemonJVMOptions::maxMetaspaceSize, listOf("XX:MaxMetaspaceSize"), mergeDelimiter = "="),
                       StringPropMapper(this, DaemonJVMOptions::reservedCodeCacheSize, listOf("XX:ReservedCodeCacheSize"), mergeDelimiter = "="),
                       restMapper)

    val restMapper: RestPropMapper<*, *>
        get() = RestPropMapper(this, DaemonJVMOptions::jvmParams)
}

// TODO: expose sources for testability and test properly
fun configureDaemonJVMOptions(opts: DaemonJVMOptions,
                              additionalParams: Iterable<String>,
                              inheritMemoryLimits: Boolean,
                              inheritOtherJvmOptions: Boolean,
                              inheritAdditionalProperties: Boolean
): DaemonJVMOptions {
    // note: sequence matters, e.g. explicit override in COMPILE_DAEMON_JVM_OPTIONS_PROPERTY should be done after inputArguments processing
    if (inheritMemoryLimits || inheritOtherJvmOptions) {
        val jvmArguments = ManagementFactory.getRuntimeMXBean().inputArguments
        val targetOptions = if (inheritMemoryLimits) opts else DaemonJVMOptions()
        val otherArgs = jvmArguments.filterExtractProps(targetOptions.mappers, prefix = "-")

        if (inheritMemoryLimits) {
            if (opts.maxMemory.isBlank()) {
                val maxMemBytes = Runtime.getRuntime().maxMemory()
                // rounding up
                val maxMemMegabytes = maxMemBytes / (1024 * 1024) + if (maxMemBytes % (1024 * 1024) == 0L) 0 else 1
                opts.maxMemory = "${maxMemMegabytes}m"
            }
        }

        if (inheritOtherJvmOptions) {
            opts.jvmParams.addAll(
                otherArgs.filterNot {
                    it.startsWith("agentlib") ||
                            it.startsWith("D" + CompilerSystemProperties.COMPILE_DAEMON_LOG_PATH_PROPERTY.property) ||
                            it.startsWith("D" + CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.property) ||
                            it.startsWith("D" + CompilerSystemProperties.COMPILE_DAEMON_JVM_OPTIONS_PROPERTY.property) ||
                            it.startsWith("D" + CompilerSystemProperties.COMPILE_DAEMON_OPTIONS_PROPERTY.property)
                })
        }
    }
    CompilerSystemProperties.COMPILE_DAEMON_JVM_OPTIONS_PROPERTY.value?.let {
        opts.jvmParams.addAll(
                it.trimQuotes()
                  .split("(?<!\\\\),".toRegex())  // using independent non-capturing group with negative lookahead zero length assertion to split only on non-escaped commas
                  .map { it.replace("\\\\(.)".toRegex(), "$1") } // de-escaping characters escaped by backslash, straightforward, without exceptions
                  .filterExtractProps(opts.mappers, "-", opts.restMapper))
    }

    // assuming that from the conflicting options the last one is taken
    // TODO: compare and override
    opts.jvmParams.addAll(additionalParams)

    if (inheritAdditionalProperties) {
        CompilerSystemProperties.COMPILE_DAEMON_LOG_PATH_PROPERTY.value?.let { opts.jvmParams.add("D${CompilerSystemProperties.COMPILE_DAEMON_LOG_PATH_PROPERTY.property}=\"$it\"") }
        CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.value?.let { opts.jvmParams.add("D${CompilerSystemProperties.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY.property}") }
    }

    if (opts.jvmParams.none { it.matches(jvmAssertArgsRegex) }) {
        opts.jvmParams.add("ea")
    }
    return opts
}

I looked at the history of the above file and noticed the addition of maxMetaspaceSize and removal of maxPermSize happened years ago when the community generally started using newer JDKs.

data class DaemonJVMOptions(
        var maxMemory: String = "",
        var maxPermSize: String = "",
+        var maxMetaspaceSize: String = "",
        var reservedCodeCacheSize: String = "",
        var jvmParams: MutableCollection<String> = arrayListOf()
)

Inherit max metaspace size of client VM for Kotlin compile daemon [[12]]

data class DaemonJVMOptions(
        var maxMemory: String = "",
-        var maxPermSize: String = "",
        var maxMetaspaceSize: String = "",
        var reservedCodeCacheSize: String = "",
        var jvmParams: MutableCollection<String> = arrayListOf()
)

Drop MaxPermSize support from compiler daemon [[13]]

I found the rest of the JVM args were being set in KotlinCompilerClient.kt [[14]] which also calls configureDaemonJVMOptions when the Kotlin Daemon is started. I'm honestly not 100% sure how this gets invoked and didn't want to spend more time connecting the dots since I found what I was looking for.

package org.jetbrains.kotlin.daemon.client

object KotlinCompilerClient {
    @JvmStatic
    fun main(vararg args: String) {
        val compilerId = CompilerId()
        val daemonOptions = configureDaemonOptions()
        val daemonLaunchingOptions =
            configureDaemonJVMOptions(inheritMemoryLimits = true, inheritOtherJvmOptions = false, inheritAdditionalProperties = true)


    internal data class GcAutoConfiguration(
        var shouldAutoConfigureGc: Boolean = true,
        val preferredGc: String = "Parallel"
    )

    private fun startDaemon(
        compilerId: CompilerId,
        daemonJVMOptions: DaemonJVMOptions,
        daemonOptions: DaemonOptions,
        reportingTargets: DaemonReportingTargets,
        startupAttempt: Int,
        gcAutoConfiguration: GcAutoConfiguration,
    ): Boolean {
        val javaExecutable = File(File(CompilerSystemProperties.JAVA_HOME.safeValue, "bin"), "java")
        val serverHostname = CompilerSystemProperties.JAVA_RMI_SERVER_HOSTNAME.value
            ?: error("${CompilerSystemProperties.JAVA_RMI_SERVER_HOSTNAME.property} is not set!")
        val platformSpecificOptions = listOf(
            // hide daemon window
            "-Djava.awt.headless=true",
            "-D${CompilerSystemProperties.JAVA_RMI_SERVER_HOSTNAME.property}=$serverHostname"
        )
        val javaVersion = CompilerSystemProperties.JAVA_VERSION.value?.toIntOrNull()
        val javaIllegalAccessWorkaround =
            if (javaVersion != null && javaVersion >= 16)
                listOf("--add-exports", "java.base/sun.nio.ch=ALL-UNNAMED")
            else emptyList()
        val environmentVariablesForTests = getEnvironmentVariablesForTests(reportingTargets)
        val jvmArguments = daemonJVMOptions.mappers.flatMap { it.toArgs("-") }
        if (
            (jvmArguments + getImplicitJvmArguments(environmentVariablesForTests))
                .any { it == "-XX:-Use${gcAutoConfiguration.preferredGc}GC" || (it.startsWith("-XX:+Use") && it.endsWith("GC")) }
        ) {
            // enable the preferred gc only if it's not explicitly disabled and no other GC is selected
            gcAutoConfiguration.shouldAutoConfigureGc = false
        }
        val additionalOptimizationOptions = listOfNotNull(
            "-XX:+UseCodeCacheFlushing",
            if (gcAutoConfiguration.shouldAutoConfigureGc) "-XX:+Use${gcAutoConfiguration.preferredGc}GC" else null,
        )
        val args = listOf(
            javaExecutable.absolutePath, "-cp", compilerId.compilerClasspath.joinToString(File.pathSeparator)
        ) +
                platformSpecificOptions +
                jvmArguments +
                additionalOptimizationOptions +
                javaIllegalAccessWorkaround +
                COMPILER_DAEMON_CLASS_FQN +
                daemonOptions.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) } +
                compilerId.mappers.flatMap { it.toArgs(COMPILE_DAEMON_CMDLINE_OPTIONS_PREFIX) }
        reportingTargets.report(DaemonReportCategory.INFO, "starting the daemon as: " + args.joinToString(" "))
        val processBuilder = ProcessBuilder(args)
        processBuilder.redirectErrorStream(true)
        processBuilder.environment().putAll(environmentVariablesForTests)
        val workingDir = File(daemonOptions.runFilesPath).apply { mkdirs() }
        processBuilder.directory(workingDir)
        // assuming daemon process is deaf and (mostly) silent, so do not handle streams
        val daemon = processBuilder.start()

What did we find?

The Kotlin Daemon only inherits 3 specific JVM args from its parent VM (whether that is Gradle or something else) and ignores the rest. It also sets a bunch of defaults that are all good settings.

Inherited JVM args:

  • -Xmx
  • -XX:MaxMetaspaceSize
  • -XX:ReservedCodeCacheSize
    • Defaults to 320m if not inherited and unspecified

Defaults:

  • -XX:UseParallelGC
  • -ea
  • -XX:+UseCodeCacheFlushing
  • -XX:+HeapDumpOnOutOfMemoryError
  • -Djava.awt.headless=true
  • -D{hostname.property}={localhostip}
  • --add-exports=java.base/sun.nio.ch=ALL-UNNAMED
    • Only for JDK 16+

Since I'm at DroidconNYC this week I spot checked with various Android engineers, and only the Gradle engineers told me the correct behavior. I updated the Kotlin Gradle JVM documentation to fully explain the behavior happening under the hood. Therefore if you're reading this its a good idea to check your settings, run VisualVM, and verify that your intended settings are being applied appropriately.


References:

  1. KotlinLang Docs: Kotlin JVM args property [[1]]
  2. KotlinLang Docs: Gradle daemon arguments inheritance [[2]]
  3. KotlinLang Slack #gradle [[3]]
  4. PropertiesProvider.kt [[4]]
  5. AbstractKotlinCompileConfig.kt [[5]]
  6. CompileUsingKotlinDaemonWithNormalization.kt [[6]]
  7. AbstractKotlinCompile.kt [[7]]
  8. CompilerExecutionSettings.kt [[8]]
  9. GradleKotlinCompilerWork.kt [[9]]
  10. GradleKotlinCompilerRunner.kt [[10]]
  11. DaemonParams.kt [[11]]
  12. DaemonParams.kt Commit History - Jul 25, 2019 [[12]]
  13. DaemonParams.kt Commit History - Jul 29, 2019 [[13]]
  14. KotlinCompilerClient.kt [[14]]

[[1]]: KotlinLang Docs: Kotlin JVM args property https://kotlinlang.org/docs/gradle-compilation-and-caches.html#kotlin-daemon-jvmargs-property

[[2]]: KotlinLang Docs: Gradle daemon arguments inheritance https://kotlinlang.org/docs/gradle-compilation-and-caches.html#gradle-daemon-arguments-inheritance

[[3]]: KotlinLang Slack #gradle https://kotlinlang.slack.com/archives/C19FD9681/p1585046690028700

[[4]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt

[[5]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/AbstractKotlinCompileConfig.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/configuration/AbstractKotlinCompileConfig.kt

[[6]]: https://www.github.com/JetBrains/kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/CompileUsingKotlinDaemonWithNormalization.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/CompileUsingKotlinDaemonWithNormalization.kt

[[7]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/AbstractKotlinCompile.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/tasks/AbstractKotlinCompile.kt

[[8]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/CompilerExecutionSettings.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/CompilerExecutionSettings.kt

[[9]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerWork.kt

[[10]]: kotlin/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt https://www.github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt

[[11]]: compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt https://github.com/JetBrains/kotlin/blob/master/compiler/daemon/daemon-common/src/org/jetbrains/kotlin/daemon/common/DaemonParams.kt

[[12]]: Inherit max metaspace size of client VM for Kotlin compile daemon (Jul 25, 2019) https://github.com/JetBrains/kotlin/commit/f3112f752da28f233859fd95b3a86980951a4b8b

[[13]]: Drop MaxPermSize support from compiler daemon (Jul 29, 2019) https://github.com/JetBrains/kotlin/commit/560f1483e8fa71b258e6cc205f200625f92736f5

[[14]]: compiler/daemon/daemon-client/src/main/kotlin/KotlinCompilerDaemon.kt https://github.com/JetBrains/kotlin/blob/master/compiler/daemon/src/org/jetbrains/kotlin/daemon/KotlinCompileDaemon.kt