Skip to content

Commit 08dd52f

Browse files
qwwdfsadSpace Team
authored and
Space Team
committed
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 cached values in ClassValue into SoftReference to avoid unloadable cycles ^KT-56093 Fixed
1 parent 46ddcac commit 08dd52f

File tree

1 file changed

+32
-10
lines changed

1 file changed

+32
-10
lines changed

core/reflection.jvm/src/kotlin/reflect/jvm/internal/CacheByClass.kt

+32-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package kotlin.reflect.jvm.internal
77

8+
import java.lang.ref.SoftReference
89
import java.util.concurrent.ConcurrentHashMap
910

1011
/*
@@ -26,7 +27,7 @@ internal abstract class CacheByClass<V> {
2627
}
2728

2829
/**
29-
* Creates a **strongly referenced** cache of values associated with [Class].
30+
* Creates a **softly referenced** cache of values associated with [Class].
3031
* Values are computed using provided [compute] function.
3132
*
3233
* `null` values are not supported, though there aren't any technical limitations.
@@ -35,25 +36,46 @@ internal fun <V : Any> createCache(compute: (Class<*>) -> V): CacheByClass<V> {
3536
return if (useClassValue) ClassValueCache(compute) else ConcurrentHashMapCache(compute)
3637
}
3738

38-
private class ClassValueCache<V>(private val compute: (Class<*>) -> V) : CacheByClass<V>() {
39+
/*
40+
* We can only cache SoftReference instances in our own classvalue to avoid classloader-based leak.
41+
*
42+
* In short, the following uncollectable cycle is possible otherwise:
43+
* ClassValue -> KPackageImpl.getClass() -> UrlClassloader -> all loaded classes by this CL ->
44+
* -> kotlin.reflect.jvm.internal.ClassValueCache -> ClassValue
45+
*/
46+
private class ComputableClassValue<V>(@JvmField val compute: (Class<*>) -> V) : ClassValue<SoftReference<V>>() {
47+
override fun computeValue(type: Class<*>): SoftReference<V> {
48+
return SoftReference(compute(type))
49+
}
50+
51+
fun createNewCopy() = ComputableClassValue(compute)
52+
}
53+
54+
private class ClassValueCache<V>(compute: (Class<*>) -> V) : CacheByClass<V>() {
3955

4056
@Volatile
41-
private var classValue = initClassValue()
57+
private var classValue = ComputableClassValue(compute)
4258

43-
private fun initClassValue() = object : ClassValue<V>() {
44-
override fun computeValue(type: Class<*>): V {
45-
return compute(type)
46-
}
59+
override fun get(key: Class<*>): V {
60+
val classValue = classValue
61+
classValue[key].get()?.let { return it }
62+
// Clean stale value if it was collected at some point
63+
classValue.remove(key)
64+
/*
65+
* Optimistic assumption: the value was invalidated at some point of time,
66+
* but now we do not have a memory pressure and can recompute the value
67+
*/
68+
classValue[key].get()?.let { return it }
69+
// Assumption failed, do not retry to avoid any non-trivial GC-dependent loops and deliberately create a separate copy
70+
return classValue.compute(key)
4771
}
4872

49-
override fun get(key: Class<*>): V = classValue[key]
50-
5173
override fun clear() {
5274
/*
5375
* ClassValue does not have a proper `clear()` method but is properly weak-referenced,
5476
* thus abandoning ClassValue instance will eventually clear all associated values.
5577
*/
56-
classValue = initClassValue()
78+
classValue = classValue.createNewCopy()
5779
}
5880
}
5981

0 commit comments

Comments
 (0)