skip to content

What is the 'new memory manager' in Kotlin/Native, and what old model did it replace?

level: juniorimportance: must knowfreq 62%

answer

  1. Tracing GC replaced freeze()/ownership
  2. Default in 1.7.20, only option in 1.9.20
  3. Shared mutable state now allowed
  4. @SharedImmutable deprecated
  5. Still need synchronization for races

basics

~10 s

It is Kotlin/Native's modern garbage collector. It replaced an old rule that froze objects and locked them to one thread. Now you can share regular mutable objects across threads, like on the JVM.

solid answer

~40 s

The new memory manager (default since Kotlin 1.7.20, the only option since 1.9.20) is a tracing garbage collector for Kotlin/Native. It removed the legacy 'strict' model where objects were thread-confined and you had to call freeze() to share them, after which any mutation threw InvalidMutabilityException. Markers like @SharedImmutable and @ThreadLocal-driven workarounds are gone or deprecated. With the new manager, ordinary mutable objects can be referenced and mutated from multiple threads, so Kotlin/Native concurrency behaves much closer to the JVM. You no longer freeze top-level state, and coroutines work across threads without InvalidMutabilityException or IncorrectDereferenceException surprises. You still need normal synchronization (locks, atomics) to avoid data races; the GC removes ownership restrictions, not the need for correct concurrent code.

code

kotlin · 15 lines
kotlin
// Legacy Kotlin/Native (pre-new-manager)
import kotlin.native.concurrent.freeze

data class Config(var url: String)

fun legacyShare() {
    val cfg = Config("a").freeze() // deep-immutable
    // cfg.url = "b"  // -> InvalidMutabilityException at runtime
}

// New memory manager: no freeze needed, stays mutable
fun modernShare() {
    val cfg = Config("a")
    cfg.url = "b" // fine, even when shared across threads
}

go deeper

for a junior

Knows the new manager is a GC that replaced freeze() and now allows shared mutable objects across threads.

for a middle

Can name the legacy exceptions (InvalidMutabilityException) and that synchronization is still required.

for a senior

Recalls the version timeline (1.7.20 default, 1.9.20 only) and the deprecated annotations like @SharedImmutable/@ThreadLocal.

for a principal

Frames the migration impact for a shared KMP library and contrasts safety-by-construction (freezing) vs. GC + explicit synchronization tradeoffs.

## What the question is about Kotlin/Native compiles Kotlin to native binaries (iOS, macOS, Linux, Windows) with no JVM. It needs its own memory management. Historically it used a unique, restrictive model; the 'new memory manager' replaced it with a conventional garbage collector. ## The old (legacy) model Before the new manager, Kotlin/Native enforced **thread ownership** of objects: - Each object graph was 'owned' by the thread that created it. Another thread could not freely access it. - To share an object across threads you had to **freeze** it with the `freeze()` extension function, making the whole reachable graph deeply immutable. - Mutating a frozen object threw **`InvalidMutabilityException`**. - Accessing an unfrozen, foreign-thread object threw **`IncorrectDereferenceException`**. - Annotations like **`@SharedImmutable`** (a top-level `val` shared across threads, implicitly frozen) and **`@ThreadLocal`** (per-thread copy of top-level state) existed to work around these rules. This 'object freezing' model was safe-by-construction but painful: shared caches, singletons, and multithreaded coroutines were awkward, and many JVM-friendly libraries did not port cleanly. ## The new memory manager The new manager is a **tracing garbage collector** (stop-the-world mark phase, concurrent sweep in modern versions). Key effects: - **No more freezing.** `freeze()` is deprecated/no-op; objects stay mutable. - **Shared mutable state across threads is allowed.** A `var` in a top-level object can be read/written from multiple threads. - `InvalidMutabilityException` and `IncorrectDereferenceException` effectively disappear in normal code. - `@SharedImmutable` is deprecated; `@ThreadLocal` still exists but is rarely needed. ## Timeline (know roughly) - **1.6.0**: available behind a flag (preview). - **1.7.20**: **default**. - **1.9.20**: legacy manager **removed**; new manager is the only option. ```kotlin // Old world: this needed freeze() and any mutation later threw InvalidMutabilityException object Counter { var value: Int = 0 // legacy: mutating from another thread => error } // New world: ordinary mutable shared state is allowed across threads fun bump() { Counter.value += 1 // compiles and runs on any thread } ``` ## Important caveat The new manager removes **ownership** restrictions, not **concurrency correctness**. `Counter.value += 1` from many threads is still a **data race**. You must use proper synchronization — e.g. `kotlin.concurrent.AtomicInt`, `Mutex`, or platform locks. The GC frees you from freezing, not from thinking.

  • Does the new manager mean you no longer need synchronization?
    No. It removes thread-ownership/freezing, but concurrent mutation of shared state is still a data race. Use atomics, Mutex, or locks.
  • Which Kotlin version made the new manager the default?
    Kotlin 1.7.20 made it the default; 1.9.20 removed the legacy manager entirely.

The old model was a museum: to let others see an exhibit you sealed it in glass (freeze) forever. The new model is an open library: anyone can touch the books, but you still need rules so two people don't scribble at once.

saying these in an interview costs you the question

  • Thinking the new GC also handles thread synchronization automatically
  • Believing freeze() is still required to share objects
  • Confusing Kotlin/Native GC with the JVM's GC implementation
  • Saying it was always tracing and freeze() never existed
  • Claiming shared mutable state is impossible on Native

context