Coming soon! See https://github.com/flutter/devtools/issues/3951.
The text below is under construction.
This page describes how to troubleshoot memory leaks. See other information on memory leaks here.
If leak tracker detected a leak in your application or test, first check if the leak matches a known simple case, and, if no, switch to more complicated troubleshooting.
Follow the rules to avoid/fix notGCed and notDisposed leaks:
dispose.dispose happens earlier than owner's disposal.A test specific rule:
tearDown, so that test failure does not result in a leak:final FocusNode focusNode = FocusNode(); addTearDown(focusNode.dispose());
TODO: add example and steps.
If your code creates an OverlayEntry, it should both remove and dispose it:
final OverlayEntry overlayEntry = OverlayEntry(...); addTearDown(() => overlayEntry..remove()..dispose());
If your test starts a test gesture, make sure to finish it to release resources:
final TestGesture gesture = await tester.startGesture( ... // Finish gesture to release resources. await tester.pumpAndSettle(); await gesture.up(); await tester.pumpAndSettle();
To understand the root cause of a memory leak, you may want to gather additional information.
not-disposed:
not-GCed or GCed-late:
Allocation and disposal call-stacks: helps to understand lifecycle of the object which may reveal where the object is being held from garbage collection.
Other lifecycle events: TODO: add content
Retaining path: shows which objects hold the leaked one from garbage collection.
By default, the leak tracker does not gather the information, because the collection may impact performance and memory footprint.
Tests
For collecting debugging information in tests, temporarily pass an instance of LeakTrackingTestConfig, specific for the debugged leak type, to the test:
testWidgetsWithLeakTracking('My test', (WidgetTester tester) async { ... }, leakTrackingTestConfig: LeakTrackingTestConfig.debugNotGCed());
Applications
For collecting debugging information in your running application, the options are:
LeakTrackingConfiguration to enableLeakTrackingTODO: link DevTools documentation with explanation
If you expect an object to be not referenced at some point, but not sure, you can validate it by temporarily adding assertion.
final ref = WeakReference(myObject); myObject = null; await forceGC(); if (ref.target == null) { throw StateError('Validated that myObject is not held from garbage collection.'); } else { print(await formattedRetainingPath(ref)); throw StateError('myObject is reachable from root. See console output for the retaining path.'); }
IMPORTANT: this code will not work in release mode, so you need to run it with flag --debug or --profile (not available for Flutter tests), or, if it is a test, by clicking Debug near the test name in IDE.
[ChangeNotifier] is disposable and is tracked by leak_tracker.
But, as it is mixin, it does not have its own constructor. So, it communicates object creation in first addListener, that results in creation stack trace pointing to addListener, not to constructor.
To make debugging easier, invoke [ChangeNotifier.maybeDispatchObjectCreation] in constructor of the class. It will help to identify the owner in case of leaks.
If you see notGCed leaks, where the retaining path starts with global or static variable, this means that some objects were disposed, but references to them were never released.
root -> staticA -> B -> C -> disposedD
In this example, disposedD should stop being reachable from the root. You need to find the closest to the root object, that is not needed any more and release reference to it, that will make the entire chain after available for garbage collection.
There are ways to release the reference:
void dispose() { disposedD.dispose(); }
disposedD?.dispose(); disposedD = null;
class C { ... final WeakReference<MyClass> disposedD; ... }
If a method contains more than one closures, they share the context and thus all instances of the context will be alive while at least one of the closures is alive.
TODO: add example
Such cases are hard to troubleshoot. One way to fix them is to convert all closures, which reference the leaked type, to named methods.