diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-02-06 13:55:49 +0100 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-03-11 15:18:22 -0700 |
commit | 57c07204ca452564b930085cfa9e8b099e45b2a9 (patch) | |
tree | 00b74b093e1201c31406d256a138beca4c1c71a0 /src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala | |
parent | a4e71b188fe8069b4de3a0753defb624b8b1eb8c (diff) | |
download | scala-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.scala | 37 |
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)) } /** |