Problem
PlanCompiler.plans (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/PlanCompiler.kt:39) and KotlinReflectMemberScanner.scanCache (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/MemberScanner.kt:81) are plain ConcurrentHashMap<KClass<*>, ...> with no size bound, TTL, or weak/soft keys. Both live inside the process-lifetime KuriBind.executor singleton (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/KuriBind.kt:50).
A KClass<*> strongly retains its backing java.lang.Class, which in turn strongly retains its defining ClassLoader. Caching plans/scans keyed by every distinct KClass seen means every distinct class that has ever been bound is retained — and with it, its ClassLoader — for as long as the process runs.
The leak surface is broader than just the annotated request/response types passed at the API boundary: runtimeValueIsBindable routes the runtime class of arbitrary nested values through planFor, so nested value types also get cached.
Impact
In a long-running host that creates classes dynamically per unit of work — an application server with per-deployment ClassLoaders, a scripting engine, bytecode-generated proxies, hot code redeploy — binding a stream of distinct generated classes accumulates one plan+scan entry per Class forever. The strong Class keys prevent those ClassLoaders from ever being garbage collected, leaking metaspace and ClassLoader instances for the life of the process.
Suggested fix
Bound the caches (e.g. an LRU with a fixed max size) or use weak keys (WeakHashMap/Caffeine-style weak-key cache) so a KClass that's no longer referenced elsewhere can be collected along with its ClassLoader.
Problem
PlanCompiler.plans(kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/PlanCompiler.kt:39) andKotlinReflectMemberScanner.scanCache(kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/internal/MemberScanner.kt:81) are plainConcurrentHashMap<KClass<*>, ...>with no size bound, TTL, or weak/soft keys. Both live inside the process-lifetimeKuriBind.executorsingleton (kuri-bind/src/jvmMain/kotlin/org/dexpace/kuri/bind/KuriBind.kt:50).A
KClass<*>strongly retains its backingjava.lang.Class, which in turn strongly retains its definingClassLoader. Caching plans/scans keyed by every distinctKClassseen means every distinct class that has ever been bound is retained — and with it, its ClassLoader — for as long as the process runs.The leak surface is broader than just the annotated request/response types passed at the API boundary:
runtimeValueIsBindableroutes the runtime class of arbitrary nested values throughplanFor, so nested value types also get cached.Impact
In a long-running host that creates classes dynamically per unit of work — an application server with per-deployment ClassLoaders, a scripting engine, bytecode-generated proxies, hot code redeploy — binding a stream of distinct generated classes accumulates one plan+scan entry per
Classforever. The strongClasskeys prevent those ClassLoaders from ever being garbage collected, leaking metaspace and ClassLoader instances for the life of the process.Suggested fix
Bound the caches (e.g. an LRU with a fixed max size) or use weak keys (
WeakHashMap/Caffeine-style weak-key cache) so aKClassthat's no longer referenced elsewhere can be collected along with its ClassLoader.