Address classloader leak in a recently-introduced ClassValueCache
Root cause:
Cached in `ClassValue` values are prohibited to reference any `java.lang.Class` or `ClassValue` instances as they are considered as strong roots that prevent `Class` and `ClassValue` garbage collection,
creating effectively unloadable cycle.
This problem is also known as JDK-8136353.
Actions taken:
* Extract anonymous `ClassValue` instance into a separate *static* class that does not capture anything implicitly
* Wrap potentially leaking direct `Class` references into WeakReference
* Wrap complex descriptor-wrapping structures into LazySoft to minimize the codebase impact and cover all at once
Actions not taken:
* Each individual class from core:descriptors.runtime that references `java.lang.Class` is not modified.
These classes are referenced by already soft-wrapped Data and are not cached otherwise
* Cached values are not wrapped into WeakReference. Even the simplest experiment shows significant GC pressure
(that previous implementation was also prone to)
^KT-56093 Fixed
diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/CacheByClass.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/CacheByClass.kt
index f95e0cc..6343ce8 100644
--- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/CacheByClass.kt
+++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/CacheByClass.kt
@@ -35,16 +35,18 @@
return if (useClassValue) ClassValueCache(compute) else ConcurrentHashMapCache(compute)
}
-private class ClassValueCache<V>(private val compute: (Class<*>) -> V) : CacheByClass<V>() {
+private class ComputableClassValue<V>(private val compute: (Class<*>) -> V) : ClassValue<V>() {
+ override fun computeValue(type: Class<*>): V {
+ return compute(type)
+ }
+
+ fun createNewCopy() = ComputableClassValue(compute)
+}
+
+private class ClassValueCache<V>(compute: (Class<*>) -> V) : CacheByClass<V>() {
@Volatile
- private var classValue = initClassValue()
-
- private fun initClassValue() = object : ClassValue<V>() {
- override fun computeValue(type: Class<*>): V {
- return compute(type)
- }
- }
+ private var classValue = ComputableClassValue(compute)
override fun get(key: Class<*>): V = classValue[key]
@@ -53,7 +55,7 @@
* ClassValue does not have a proper `clear()` method but is properly weak-referenced,
* thus abandoning ClassValue instance will eventually clear all associated values.
*/
- classValue = initClassValue()
+ classValue = classValue.createNewCopy()
}
}
diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt
index a7af316..ab55f65 100644
--- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt
+++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt
@@ -36,13 +36,14 @@
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.utils.compact
+import java.lang.ref.WeakReference
import kotlin.jvm.internal.TypeIntrinsics
import kotlin.reflect.*
import kotlin.reflect.jvm.internal.KDeclarationContainerImpl.MemberBelonginess.DECLARED
import kotlin.reflect.jvm.internal.KDeclarationContainerImpl.MemberBelonginess.INHERITED
internal class KClassImpl<T : Any>(
- override val jClass: Class<T>
+ jClass: Class<T>
) : KDeclarationContainerImpl(), KClass<T>, KClassifierImpl, KTypeParameterOwnerImpl {
inner class Data : KDeclarationContainerImpl.Data() {
val descriptor: ClassDescriptor by ReflectProperties.lazySoft {
@@ -177,7 +178,11 @@
by ReflectProperties.lazySoft { allNonStaticMembers + allStaticMembers }
}
- val data = ReflectProperties.lazy { Data() }
+ private val weaklyReferencedJClass = WeakReference(jClass)
+
+ override val jClass: Class<T> get() = weaklyReferencedJClass.get() ?: nullClassWeakReference()
+
+ val data = ReflectProperties.lazySoft { Data() }
override val descriptor: ClassDescriptor get() = data().descriptor
diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt
index c3afae8..db5dd42 100644
--- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt
+++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt
@@ -32,11 +32,12 @@
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
+import java.lang.ref.WeakReference
import kotlin.reflect.KCallable
import kotlin.reflect.jvm.internal.KDeclarationContainerImpl.MemberBelonginess.DECLARED
internal class KPackageImpl(
- override val jClass: Class<*>,
+ jClass: Class<*>,
) : KDeclarationContainerImpl() {
private inner class Data : KDeclarationContainerImpl.Data() {
private val kotlinClass: ReflectKotlinClass? by ReflectProperties.lazySoft {
@@ -51,7 +52,7 @@
else MemberScope.Empty
}
- val multifileFacade: Class<*>? by ReflectProperties.lazy {
+ val multifileFacade: Class<*>? by ReflectProperties.lazySoft {
val facadeName = kotlinClass?.classHeader?.multifileClassName
// We need to check isNotEmpty because this is the value read from the annotation which cannot be null.
// The default value for 'xs' is empty string, as declared in kotlin.Metadata
@@ -76,7 +77,11 @@
}
}
- private val data = ReflectProperties.lazy { Data() }
+ private val weaklyReferencedJClass: WeakReference<Class<*>> = WeakReference(jClass)
+
+ override val jClass: Class<*> get() = weaklyReferencedJClass.get() ?: nullClassWeakReference()
+
+ private val data = ReflectProperties.lazySoft { Data() }
override val methodOwner: Class<*> get() = data().multifileFacade ?: jClass
diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt
index f654b0a..5c4fa92 100644
--- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt
+++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt
@@ -47,6 +47,7 @@
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
import org.jetbrains.kotlin.serialization.deserialization.DeserializedArrayValue
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
+import java.lang.ref.WeakReference
import java.lang.reflect.Type
import kotlin.jvm.internal.FunctionReference
import kotlin.jvm.internal.PropertyReference
@@ -298,3 +299,6 @@
override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, data: Unit): KCallableImpl<*> =
KFunctionImpl(container, descriptor)
}
+
+internal fun nullClassWeakReference(): Nothing =
+ error("Unexpected 'null' in WeakReference<Class> that indicates bug in reflection caching in the presence of multiple classloaders")