跪拜 Guibai
← All articles
Kotlin · Android

Memory Leaks Outlast RAII, GC, and Rust Because Runtimes Can't Judge Intent

By RockByte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A developer who waits for a language or GC to make memory leaks impossible will keep waiting: the remaining leaks live in cache and lifecycle code where the program cannot infer intent. Retention policy, ownership direction, and eviction rules are explicit design decisions, not compiler features.

Summary

RAII, garbage collectors, and Rust's ownership model have eliminated a large class of accidental leaks: a C++ pointer lost before `free` becomes an automatic drop at scope exit. A second class remains. Java's GC can trace reachability, yet a static cache can hold 1 MiB arrays the program will never use again; a Kotlin image cache raises the same issue in business terms. When should an entry die? After 10 seconds, after 100 MB, when the page closes, or never? The answer is a business judgement.

Rust does not close the gap. Two `Rc` nodes referencing each other form a cycle whose strong reference counts never hit zero, so destructors never run; `Weak` only helps when the programmer already knows which edge should not own. Rust also permits `std::mem::forget`, and memory leaks sit outside its memory-safety guarantees.

The theoretical limit is the halting problem: knowing whether a reachable object will be used again can require knowing whether a loop terminates. No allocator, compiler, or GC can answer "how long should this object live" in general. Current tooling solves forgotten frees, dangling pointers, and unreachable garbage, but not useless-but-reachable objects or guaranteed bounded memory growth; those decisions belong to cache strategy and resource budgets.

Takeaways
— A C++ function that does `new int(100)` without `delete` loses the pointer at scope exit, so the heap allocation can never be freed; RAII and Rust's `Box` avoid this by tying memory lifetime to scope.
— Java's GC reclaims unreachable objects only; a static `List<byte[]>` cache can keep growing with 1 MiB arrays that the program will never touch again, a logical memory leak.
— In Kotlin, a global image cache needs eviction rules (idle time, memory threshold, page close, future use) that no compiler, GC, or OS can derive from code alone.
— Two Rust `Rc` nodes referencing each other through `RefCell` form a cycle; their strong counts never reach zero, so destructors don't run and heap memory is not freed by normal destruction.
— Rust's `Weak` breaks such a cycle only when the programmer decides which reference should not own the object; `std::mem::forget` can also skip destruction, and leaks are outside Rust's memory-safety guarantees.
— A universal algorithm that decides whether a reachable object will be used again would require solving the halting problem, so no fully general solution exists; static analysis, runtime analysis, and language constraints can still catch many leaks.
— The practical goals already solved are forgotten `free`, dangling pointers/double frees, and unreachable-object reclamation; the unsolved goal is identifying useless-but-reachable objects and keeping memory growth bounded, which requires cache policy and business requirements.
Conclusions

The halting-problem framing keeps the discussion honest: it does not claim all leaks are undetectable, only that a universal detector for arbitrary programs is impossible. Targeted analysis and language constraints still catch many leaks.

Rust's safety guarantee is narrower than the language is often marketed as: `Rc` cycles and `std::mem::forget` are legal, and leaks sit outside the memory-safety promise, so ownership alone is not a final answer.

Deciding whether an object should still exist turns memory management into product design. Cache eviction, retention windows, and resource budgets are business calls, not allocator features.

Concepts & terms
RAII
Resource Acquisition Is Initialization: a C++ idiom where resource lifetime is bound to object lifetime, so destructors run automatically when a variable goes out of scope and memory is freed without an explicit call.
Logical memory leak
Memory that is still reachable and therefore not collectable, but is no longer needed by the application. The program keeps holding it, so memory usage grows even though no access path was lost.
Halting problem
The proven impossibility of a general algorithm that decides whether any arbitrary program will finish or run forever. The article uses it to explain why no universal detector can know, for every program, whether a reachable object will be used again.
Rc and RefCell
Rust's `Rc` is a reference-counted smart pointer for shared ownership; `RefCell` enables mutating data inside such shared values. When two `Rc` values point to each other, their strong counts can never drop to zero, so the memory is never freed by normal destruction.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗