Skip to content

Commit 8038b08

Browse files
committed
fix: lower default old gen heap size on Android to memory class
Sets the default old gen heap size to the device's memory class (or large memory class if large heap is enabled) instead of half of the total physical memory, which respects Android OS limits and reduces the chance of OOM. Resolves #72546
1 parent 1f213bb commit 8038b08

3 files changed

Lines changed: 132 additions & 10 deletions

File tree

engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -491,12 +491,14 @@ void ensureInitializationComplete(
491491
}
492492

493493
if (!oldGenHeapSizeSet) {
494-
// Default to half of total memory.
494+
// Default to the memory class of the device (or large memory class if large heap is
495+
// enabled)
496+
// to avoid out-of-memory issues from overly large default heap sizes.
495497
ActivityManager activityManager =
496498
(ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
497-
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
498-
activityManager.getMemoryInfo(memInfo);
499-
int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
499+
boolean isLargeHeap = (applicationInfo.flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
500+
int oldGenHeapSizeMegaBytes =
501+
isLargeHeap ? activityManager.getLargeMemoryClass() : activityManager.getMemoryClass();
500502
shellArgs.add(
501503
FlutterEngineFlags.OLD_GEN_HEAP_SIZE.engineArgument + oldGenHeapSizeMegaBytes);
502504
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine.loader;
6+
7+
import static android.os.Looper.getMainLooper;
8+
import static org.junit.Assert.assertFalse;
9+
import static org.junit.Assert.assertTrue;
10+
import static org.mockito.Mockito.anyInt;
11+
import static org.mockito.Mockito.anyLong;
12+
import static org.mockito.Mockito.anyString;
13+
import static org.mockito.Mockito.eq;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.times;
17+
import static org.mockito.Mockito.verify;
18+
import static org.mockito.Mockito.when;
19+
import static org.robolectric.Shadows.shadowOf;
20+
21+
import android.app.ActivityManager;
22+
import android.content.Context;
23+
import androidx.test.core.app.ApplicationProvider;
24+
import androidx.test.ext.junit.runners.AndroidJUnit4;
25+
import io.flutter.embedding.engine.FlutterJNI;
26+
import java.util.Arrays;
27+
import java.util.List;
28+
import org.junit.Test;
29+
import org.junit.runner.RunWith;
30+
import org.mockito.ArgumentCaptor;
31+
32+
@RunWith(AndroidJUnit4.class)
33+
public class FlutterLoaderOldGenHeapSizeReproduceTest {
34+
private final Context ctx = ApplicationProvider.getApplicationContext();
35+
36+
@Test
37+
public void itUsesMemoryClassForOldGenHeapSizeWhenLargeHeapDisabled() {
38+
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
39+
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
40+
41+
Context spyContext = spy(ctx);
42+
when(spyContext.getApplicationContext()).thenReturn(spyContext);
43+
44+
// Ensure large heap is disabled
45+
spyContext.getApplicationInfo().flags &= ~android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP;
46+
47+
ActivityManager mockActivityManager = mock(ActivityManager.class);
48+
when(mockActivityManager.getMemoryClass()).thenReturn(128);
49+
when(mockActivityManager.getLargeMemoryClass()).thenReturn(512);
50+
when(spyContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mockActivityManager);
51+
52+
assertFalse(flutterLoader.initialized());
53+
flutterLoader.startInitialization(spyContext);
54+
flutterLoader.ensureInitializationComplete(spyContext, null);
55+
shadowOf(getMainLooper()).idle();
56+
57+
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
58+
verify(mockFlutterJNI, times(1))
59+
.init(
60+
eq(spyContext),
61+
shellArgsCaptor.capture(),
62+
anyString(),
63+
anyString(),
64+
anyString(),
65+
anyLong(),
66+
anyInt());
67+
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
68+
69+
// This assertion should FAIL in the current implementation because the current implementation
70+
// sets the heap size to total physical memory / 2 (e.g. ActivityManager.MemoryInfo.totalMem /
71+
// 2)
72+
// instead of 128 (memory class).
73+
assertTrue(
74+
"Expected shell arguments to contain '--old-gen-heap-size=128', but got: " + arguments,
75+
arguments.contains("--old-gen-heap-size=128"));
76+
}
77+
78+
@Test
79+
public void itUsesLargeMemoryClassForOldGenHeapSizeWhenLargeHeapEnabled() {
80+
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
81+
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
82+
83+
Context spyContext = spy(ctx);
84+
when(spyContext.getApplicationContext()).thenReturn(spyContext);
85+
86+
// Ensure large heap is enabled
87+
spyContext.getApplicationInfo().flags |= android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP;
88+
89+
ActivityManager mockActivityManager = mock(ActivityManager.class);
90+
when(mockActivityManager.getMemoryClass()).thenReturn(128);
91+
when(mockActivityManager.getLargeMemoryClass()).thenReturn(512);
92+
when(spyContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mockActivityManager);
93+
94+
assertFalse(flutterLoader.initialized());
95+
flutterLoader.startInitialization(spyContext);
96+
flutterLoader.ensureInitializationComplete(spyContext, null);
97+
shadowOf(getMainLooper()).idle();
98+
99+
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
100+
verify(mockFlutterJNI, times(1))
101+
.init(
102+
eq(spyContext),
103+
shellArgsCaptor.capture(),
104+
anyString(),
105+
anyString(),
106+
anyString(),
107+
anyLong(),
108+
anyInt());
109+
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
110+
111+
// This assertion should FAIL in the current implementation because the current implementation
112+
// sets the heap size to total physical memory / 2 (e.g. ActivityManager.MemoryInfo.totalMem /
113+
// 2)
114+
// instead of 512 (large memory class).
115+
assertTrue(
116+
"Expected shell arguments to contain '--old-gen-heap-size=512', but got: " + arguments,
117+
arguments.contains("--old-gen-heap-size=512"));
118+
}
119+
}

engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import android.app.ActivityManager;
2828
import android.content.Context;
29+
import android.content.pm.ApplicationInfo;
2930
import android.os.Bundle;
3031
import android.util.DisplayMetrics;
3132
import androidx.test.core.app.ApplicationProvider;
@@ -134,9 +135,9 @@ public void itDefaultsTheOldGenHeapSizeAppropriately() {
134135

135136
ActivityManager activityManager =
136137
(ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
137-
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
138-
activityManager.getMemoryInfo(memInfo);
139-
int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
138+
boolean isLargeHeap = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
139+
int oldGenHeapSizeMegaBytes =
140+
isLargeHeap ? activityManager.getLargeMemoryClass() : activityManager.getMemoryClass();
140141
final String oldGenHeapArg = "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes;
141142
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
142143
verify(mockFlutterJNI, times(1))
@@ -843,9 +844,9 @@ public void itSetsOldGenHeapSizeFromMetadata() {
843844
// is configured via the manifest.
844845
ActivityManager activityManager =
845846
(ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
846-
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
847-
activityManager.getMemoryInfo(memInfo);
848-
int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
847+
boolean isLargeHeap = (ctx.getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
848+
int oldGenHeapSizeMegaBytes =
849+
isLargeHeap ? activityManager.getLargeMemoryClass() : activityManager.getMemoryClass();
849850
testFlagFromMetadataNotPresent(
850851
"io.flutter.embedding.android.OldGenHeapSize",
851852
expectedOldGenHeapSize,

0 commit comments

Comments
 (0)