Closed jgold6 closed 3 months ago
Tagging subscribers to this area: @dotnet/gc See info in area-owners.md if you want to be subscribed.
Tagging subscribers to this area: @brzvlad See info in area-owners.md if you want to be subscribed.
I investigated this in maccatalyst and it is not really a leak. If GCs are not triggered manually the app size will increase but only up to a certain point. The problem is that fully collecting the image/label is done late, after multiple GCs.
In order to futher illustrate this problem I tweaked the GridLeakPage.xaml
to have the following layout:
<Grid>
<Grid>
<Grid>
<Image
Source="dotnet_bot_large.png" />
</Grid>
</Grid>
</Grid>
I logged finalizer execution and once we navigate away from the page the following objects have finalizers run. After GC1 GridLeakPage
is collected, after another gc we have a LayoutView
finalizer run, with 2 more objects of type LayoutView
finalized after 2 more GCs. Only after that we have the finalizer for MauiImageView
run (which I suspect might hold some native memory around). So in this case, the image has its finalizer run only after 5 GC collections.
In normal C# application, if you have a chain of references like FinObj1 -> FinObj2 -> FinObj3 ... and FinObj1 is eligible for finalization, then all objects are going to be finalized together and at the next collection they are all dead. However, in this case, MauiImageView
, LayoutView
etc are bridge objects with ObjC counterpart and I think there are references between them on ObjC side. MauiImageView
is kept alive by a strong gc handle in ObjC in an UIView
. This UIView
object is probably referenced from some other ObjC that can only die once its C# counterpart is collected so we need 2 GC cycles. With longer chains of references crossing ObjC world we would need increasingly more GC collections to be able to reclaim all memory. I'm not sure whether this is fixable.
Seems like a reason for the memory usage is that some of these controls can end up using a lot of memory on the native side. I think the recommendation in these cases is to use the GC.AddMemoryPressure
api. I'd expect this to lead to more GC collections if memory grows too much.
cc @dalexsoto @rolfbjarne @Redth
For the OP: You may want to read the documentation on DisconnectHandler
and then look at https://github.com/AdamEssenmacher/MemoryToolkit.Maui. For practical purposes that may give you an immediate solution. There's going to be a change to the behavior in upcoming MAUI release in .NET 9.
Description
Any contents in a Grid will slowly leak if garbage collection is allowed to run without ever being manually called with GC.Collect(). The app will eventually crash when navigating to and away from the page with the Grid; how fast depending on how big, memory-wise, the objects in the Grid are. Observing the native memory in XCode Instruments reveals garbage collection running and collecting most, but not all, native memory of the type(s) in the Grid, e.g. MauiImage or MauiLabel. If GC.Collect is called manually, however, the leak does not appear to occur and the app never crashes, nor do I see the same consistent overall memory growth as I do when the garbage collector runs on its own according to its algorithm. No managed memory leak is observed according to memory usage reported by the GC API.
Reproduction Steps
MemoryLeakTestGridLeak.zip
Expected behavior
App will continue to run indefinitely without out of memory native crash.
Actual behavior
App crashes with native out of memory error. It will take about an hour as is. Right now the Label is the only content of the Grid. Add the commented images back in and the app will crash much faster. See comments in GridLeakPage.xaml:
Regression?
Unknown
Known Workarounds
Manually call GC.Collect() when navigating away from the page with the Grid.
Configuration
.NET 8 MAUI iOS 17.5.1 ARM64 Issue does not occur on Android using .NET 8 MAUI
Other information
I discovered that the app will run much longer if not run under Instruments, to the point where I thought perhaps Instruments was the cause. But by upping the number of Image elements to 16 from 4, the app crashed in about 2 minutes when not running under instruments.