Android framework version
net10.0-android
Affected platform version
NET 10.0
Description
I am seeing a significant difference in Java/ART GC behavior between a regular Mono-based Android build and a NativeAOT Android build.
When the application is built without NativeAOT and runs on the Mono runtime, scrolling works smoothly and explicit Java GC is rare during normal scrolling. When it does happen, it usually frees several megabytes of Java heap, so the collection appears to be justified.
However, when the same application is built with NativeAOT, scrolling causes frequent explicit Java/ART GC invocations. These collections appear to be mostly useless according to logcat output: the Java heap is around 49–50% free, only a very small amount of memory is freed, but each GC cycle still takes around 50–95ms total time.
This seems to correlate with visible micro-stutters during scrolling, especially on weaker Android devices.
During scrolling in the NativeAOT build, logcat shows repeated Explicit concurrent copying GC entries like this:
2026-06-03 11:55:00.382 6356-6404 I Explicit concurrent copying GC freed 24767(1208KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 15MB/30MB, paused 36us,55us total 66.541ms
2026-06-03 11:55:02.971 6356-6404 I Explicit concurrent copying GC freed 9771(532KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 15MB/30MB, paused 67us,56us total 53.427ms
2026-06-03 11:55:04.101 6356-6404 I Explicit concurrent copying GC freed 5453(348KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 15MB/31MB, paused 29us,49us total 49.779ms
2026-06-03 11:55:05.795 6356-6404 I Explicit concurrent copying GC freed 5715(337KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 15MB/31MB, paused 63us,54us total 62.454ms
2026-06-03 11:55:07.041 6356-6404 I Explicit concurrent copying GC freed 7200(463KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 15MB/31MB, paused 54us,50us total 62.270ms
2026-06-03 11:55:08.949 6356-6404 I Explicit concurrent copying GC freed 2224(254KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 15MB/31MB, paused 80us,56us total 79.183ms
2026-06-03 11:55:10.754 6356-6404 I Explicit concurrent copying GC freed 873(144KB) AllocSpace objects, 1(20KB) LOS objects, 49% free, 15MB/31MB, paused 49us,54us total 79.568ms
The concerning part is that these are explicit Java GC calls, but they do not seem justified by memory pressure:
49–50% free
15MB / 30–31MB heap
only ~128KB–1.2MB freed per collection
total GC time around 50–95ms
Mono behavior
In the non-NativeAOT Mono build, explicit Java GC is much less frequent during the same scrolling scenario. When it happens, it frees several megabytes of memory, so it looks much more justified:
2026-06-03 12:04:50.800 28412-28412 I Explicit concurrent copying GC freed 171675(6883KB) AllocSpace objects, 8(448KB) LOS objects, 49% free, 16MB/33MB, paused 32us,45us total 57.721ms
2026-06-03 12:06:44.926 28412-28412 I Explicit concurrent copying GC freed 97654(4619KB) AllocSpace objects, 38(1288KB) LOS objects, 50% free, 16MB/33MB, paused 34us,42us total 53.758ms
2026-06-03 12:07:15.847 28412-28412 I Explicit concurrent copying GC freed 138894(6556KB) AllocSpace objects, 25(612KB) LOS objects, 50% free, 17MB/34MB, paused 69us,50us total 64.408ms
So the difference is not just that NativeAOT logs explicit Java GC. The difference is that under Mono the GC is rare and frees around 5–7MB per collection, while under NativeAOT it can happen every 1–2 seconds during scrolling and often frees only a few hundred KB.
Is there any way to disable or tune this behavior?
Steps to Reproduce
- Create or use a .NET 10 Android application with a scrollable UI, for example a
RecyclerView with enough items to continuously scroll.
- Build and run the application with and without NativeAOT.
- Open the screen with the scrollable list and scroll for 1–2 minutes and check the difference.
Android framework version
net10.0-android
Affected platform version
NET 10.0
Description
I am seeing a significant difference in Java/ART GC behavior between a regular Mono-based Android build and a NativeAOT Android build.
When the application is built without NativeAOT and runs on the Mono runtime, scrolling works smoothly and explicit Java GC is rare during normal scrolling. When it does happen, it usually frees several megabytes of Java heap, so the collection appears to be justified.
However, when the same application is built with NativeAOT, scrolling causes frequent explicit Java/ART GC invocations. These collections appear to be mostly useless according to logcat output: the Java heap is around 49–50% free, only a very small amount of memory is freed, but each GC cycle still takes around 50–95ms total time.
This seems to correlate with visible micro-stutters during scrolling, especially on weaker Android devices.
During scrolling in the NativeAOT build, logcat shows repeated
Explicit concurrent copying GCentries like this:The concerning part is that these are explicit Java GC calls, but they do not seem justified by memory pressure:
Mono behavior
In the non-NativeAOT Mono build, explicit Java GC is much less frequent during the same scrolling scenario. When it happens, it frees several megabytes of memory, so it looks much more justified:
So the difference is not just that NativeAOT logs explicit Java GC. The difference is that under Mono the GC is rare and frees around 5–7MB per collection, while under NativeAOT it can happen every 1–2 seconds during scrolling and often frees only a few hundred KB.
Is there any way to disable or tune this behavior?
Steps to Reproduce
RecyclerViewwith enough items to continuously scroll.