From 3a6ff8afca722493647ebcdce07a841c076cd2c9 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 30 Apr 2019 12:01:48 -0700 Subject: [PATCH] Use global classloader cache for scala instance I noticed in a heap dump of sbt that there were many classloaders for the scala instance. I then realized that we were making a new classloader for the scala library on every test run. Even worse, the ScalaInstanceLoader instance was never closed which lead to a metaspace leak. I moved the scala instance classloader to the global classloader cache. Not only will these be correctly cached, they will be closed if evicted from the cache. --- .../main/scala/sbt/internal/ClassLoaders.scala | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/main/src/main/scala/sbt/internal/ClassLoaders.scala b/main/src/main/scala/sbt/internal/ClassLoaders.scala index 6366343c8..ecd4fc50a 100644 --- a/main/src/main/scala/sbt/internal/ClassLoaders.scala +++ b/main/src/main/scala/sbt/internal/ClassLoaders.scala @@ -40,6 +40,7 @@ private[sbt] object ClassLoaders { rawRuntimeDependencies = dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude), allDependencies = dependencyJars(dependencyClasspath).value.filterNot(exclude), + globalCache = (Scope.GlobalScope / classLoaderCache).value, runtimeCache = (Runtime / classLoaderCache).value, testCache = (Test / classLoaderCache).value, resources = ClasspathUtilities.createClasspathResources(fullCP, si), @@ -73,6 +74,7 @@ private[sbt] object ClassLoaders { ) s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false") } + val globalCache = (Scope.GlobalScope / classLoaderCache).value val runtimeCache = (Runtime / classLoaderCache).value val testCache = (Test / classLoaderCache).value val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.allJars @@ -86,6 +88,7 @@ private[sbt] object ClassLoaders { fullCP = classpath, rawRuntimeDependencies = runtimeDeps, allDependencies = allDeps, + globalCache = globalCache, runtimeCache = runtimeCache, testCache = testCache, resources = ClasspathUtilities.createClasspathResources(classpath, instance), @@ -114,6 +117,7 @@ private[sbt] object ClassLoaders { fullCP: Seq[File], rawRuntimeDependencies: Seq[File], allDependencies: Seq[File], + globalCache: ClassLoaderCache, runtimeCache: ClassLoaderCache, testCache: ClassLoaderCache, resources: Map[String, String], @@ -143,7 +147,8 @@ private[sbt] object ClassLoaders { val allTestDependencies = if (layerTestDependencies) allDependenciesSet else Set.empty[File] val allRuntimeDependencies = (if (layerDependencies) rawRuntimeDependencies else Nil).toSet - val scalaInstanceLayer = new ScalaInstanceLoader(si) + val scalaInstanceLayer = + globalCache.get((si.allJars.toSeq, interfaceLoader, resources, tmp)) // layer 2 val runtimeDependencySet = allDependenciesSet intersect allRuntimeDependencies val runtimeDependencies = rawRuntimeDependencies.filter(runtimeDependencySet) @@ -187,17 +192,6 @@ private[sbt] object ClassLoaders { if (snapshots.isEmpty) jarLoader else cache.get((snapshots, jarLoader, resources, tmp)) } - private class ScalaInstanceLoader(val instance: ScalaInstance) - extends URLClassLoader(instance.allJars.map(_.toURI.toURL), interfaceLoader) { - override def equals(o: Any): Boolean = o match { - case that: ScalaInstanceLoader => this.instance.allJars.sameElements(that.instance.allJars) - case _ => false - } - override def hashCode: Int = instance.hashCode - override lazy val toString: String = - s"ScalaInstanceLoader($interfaceLoader, jars = {${instance.allJars.mkString(", ")}})" - } - // helper methods private def flatLoader(classpath: Seq[File], parent: ClassLoader): ClassLoader = new URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent) {