Mobile Development 10 min read

Flutter Memory Management and Leak Prevention in the MOO Music App

The article details MOO Music’s experience with Flutter memory management, explaining the Dart heap, stack, and external memory structures, common leak sources such as missing listener deregistration, unbounded widget lists, retained timers, static references, and third‑party or engine bugs, and offers practical detection and optimization techniques.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Flutter Memory Management and Leak Prevention in the MOO Music App

MOO Music, a new music service under TME, was one of the earliest internal teams to adopt Flutter. This article series extracts the practical experiences encountered during MOO APP development, focusing on Flutter memory usage management, basic concepts, key points, troubleshooting methods, and optimization solutions. This is the first part of the series.

1. Introduction

Memory issues are a common challenge for all software development. Striving for minimal memory footprint is a developer's instinct. MOO Music adopts a Flutter hybrid architecture, gaining cross‑platform development efficiency while also facing new challenges, with memory governance being a primary focus.

2. Flutter Memory Management Mechanism

We generally consider three memory components: overall application memory, Dart heap and stack memory, and external memory.

2.1 Application Overall Memory

This includes the client platform memory as well as the memory used by the Flutter engine and the Dart VM. Monitoring overall memory changes helps quickly identify problematic modules, but it cannot pinpoint the exact code responsible.

2.2 Dart Heap and Stack Memory

Dart isolates have independent threads, heaps, and stacks, each with its own garbage collector. The heap is divided into two spaces: the new generation and the old generation (see Figure 1).

Figure 1

New Generation (New Generation) has a small memory space, split into two equal parts, managed by a copy‑clear algorithm, offering high efficiency. Surviving objects after a copy‑clear are promoted to the old generation.

Old Generation (Old Generation) has a larger memory space and follows a mark‑sweep‑compact process.

Mark Algorithm uses object reachability: the GC root maintains a list of root objects, traverses reachable objects, and marks them as live. Unmarked objects are reclaimed.

After cleaning, fragmented memory may appear, requiring compaction, which pauses all engine threads ("stop the world"). The engine mitigates GC impact through multi‑threaded parallel execution, incremental execution, and idle‑time execution. From a developer's perspective, reducing memory usage at the implementation level and performing leak detection after feature development are essential steps.

2.3 External Memory

Native objects such as files and decoded image data occupy memory outside the Dart heap. They are wrapped to be accessible from Dart. Although they do not affect Dart runtime performance directly, issues can easily cause OOM, especially when Dart objects hold references that prevent native objects from being released.

3. Common Memory Leak Scenarios

Since the engine cannot automatically determine which objects should be cleared, developers must proactively avoid leaks. Common cases include:

3.1 Listener Deregistration Missing

Image memory exceeded cache limits and was not reclaimed because a custom image handling class registered an event listener without proper deregistration. Adding deregistration in the dispose method resolved the issue.

3.2 Building Long Lists Directly

Generating all list items at once and placing them in a Column leads to OOM or severe jank when the data volume grows. The proper approach is to use list components with built‑in lazy loading (e.g., ListView.builder ) or Sliver widgets for complex structures.

3.3 Retained Closures from Timers, Animations, Futures

Objects like Animation , Timer , and Future keep strong references to captured variables until they complete. For example, a Timer.periodic callback that references context retains the MaintainInMemoryWidget instance, preventing its disposal.

Figure 2

Ensure that related cleanup or cancellation is performed when the feature exits.

3.4 Permanent Active Object References

Unless an object truly needs to persist, avoid attaching it to long‑lived objects such as root Provider models, singletons, or static variables, because their dispose methods may never be called.

3.5 Errors During Cleanup

If an exception occurs during cleanup, subsequent cleanup steps are skipped. Robust error handling is required to guarantee that all resources are released.

3.6 Third‑Party Component Quality Issues

The video_player component does not fully release memory after disposal.

Large shared_preferences data can cause high I/O and memory pressure.

Perform performance and memory testing on third‑party libraries before integration.

3.7 Flutter Engine Issues

Examples include iOS emoji rendering consuming an additional 130 MB, with pages that cannot be reclaimed, requiring engine‑level investigation.

---

Recruitment notice: QQ Music is hiring Android/iOS client developers. Click "View Original" to apply or send a resume to [email protected].

flutterMobile Developmentperformance optimizationMemory ManagementLeak Detection
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.