summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-02-06 13:55:49 +0100
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 15:18:22 -0700
commit57c07204ca452564b930085cfa9e8b099e45b2a9 (patch)
tree00b74b093e1201c31406d256a138beca4c1c71a0 /src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
parenta4e71b188fe8069b4de3a0753defb624b8b1eb8c (diff)
downloadscala-57c07204ca452564b930085cfa9e8b099e45b2a9.tar.gz
scala-57c07204ca452564b930085cfa9e8b099e45b2a9.tar.bz2
scala-57c07204ca452564b930085cfa9e8b099e45b2a9.zip
Limit the size of the ByteCodeRepository cache
I observed cases (eg Scaladoc tests) where we end up with 17k+ ClassNodes, which makes 500 MB.
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala37
1 files changed, 35 insertions, 2 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
index fb58f1b189..0958601d73 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala
@@ -17,6 +17,7 @@ import OptimizerReporting._
import BytecodeUtils._
import ByteCodeRepository._
import BTypes.InternalName
+import java.util.concurrent.atomic.AtomicLong
/**
* The ByteCodeRepository provides utilities to read the bytecode of classfiles from the compilation
@@ -26,16 +27,48 @@ import BTypes.InternalName
* @param classes Cache for parsed ClassNodes. Also stores the source of the bytecode:
* [[Classfile]] if read from `classPath`, [[CompilationUnit]] if the bytecode
* corresponds to a class being compiled.
+ * The `Long` field encodes the age of the node in the map, which allows removing
+ * old entries when the map grows too large.
* For Java classes in mixed compilation, the map contains `None`: there is no
* ClassNode generated by the backend and also no classfile that could be parsed.
*/
-class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source)]]) {
+class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val classes: collection.concurrent.Map[InternalName, Option[(ClassNode, Source, Long)]]) {
+
+ private val maxCacheSize = 1500
+ private val targetSize = 500
+
+ private val idCounter = new AtomicLong(0)
+
+ /**
+ * Prevent the code repository from growing too large. Profiling reveals that the average size
+ * of a ClassNode is about 30 kb. I observed having 17k+ classes in the cache, i.e., 500 mb.
+ *
+ * We can only remove classes with `Source == Classfile`, those can be parsed again if requested.
+ */
+ private def limitCacheSize(): Unit = {
+ if (classes.count(c => c._2.isDefined && c._2.get._2 == Classfile) > maxCacheSize) {
+ val removeId = idCounter.get - targetSize
+ val toRemove = classes.iterator.collect({
+ case (name, Some((_, Classfile, id))) if id < removeId => name
+ }).toList
+ toRemove foreach classes.remove
+ }
+ }
+
+ def add(classNode: ClassNode, source: Source) = {
+ classes(classNode.name) = Some((classNode, source, idCounter.incrementAndGet()))
+ }
+
/**
* The class node and source for an internal name. If the class node is not yet available, it is
* parsed from the classfile on the compile classpath.
*/
def classNodeAndSource(internalName: InternalName): Option[(ClassNode, Source)] = {
- classes.getOrElseUpdate(internalName, parseClass(internalName).map((_, Classfile)))
+ val r = classes.getOrElseUpdate(internalName, {
+ limitCacheSize()
+ parseClass(internalName).map((_, Classfile, idCounter.incrementAndGet()))
+ })
+ r.map(v => (v._1, v._2))
}
/**