[UPBEAT MUSIC]
VICTOR REPETSKYI: Hello.
My name is Victor Repetskyi.
I'm the tech lead of Android Games team in London.
I'm introducing the new Memory Advice API.
The new Memory Advice API is part of Android Game
Development Kit.
To remind you, AGDK is the set of tools and libraries that
provides integrated workflows, C and C++ game-related libraries
for Android, and performance optimization tools.
When Android runs out of memory, Low Memory Killer daemon
kills up to keep the system alive.
From user perspective, it is frustrating
when a game is killed by LMK.
From developers, you-- it's even hard
to tell if this is the reason of a crash.
Existing APIs, such as OnLowMemory,
are less useful for predicting low memory kills
for native apps.
Android platform has many other system-level signals in /proc
Virtual File System--
for example, /proc/meminfo.
But the signals are hard to interpret.
Moreover, these signals can be different for different devices
and different Android versions.
The new Memory Advice API provides a solution
for this problem based on a model that
analyzes system signals.
It gives a traffic light-style signal.
Green means the app can keep allocating memory.
Amber means that the app should stop allocating memory.
Red means the app should delegate memory.
The signal is available as a callback and as a direct call.
The API can be also used for data collection.
And the collected data can be used
for analyzing memory usage patterns
and adjusting allocation strategy.
What are the advantages of the API?
It shows better accuracy for predicting
LMK than onTrimMemory API for native apps.
It works across different devices and Android versions.
It has simple Java and C/C++ interfaces.
This code shows how the API can be used.
First we initialize it using JNI Environment and Activity.
Then we set up callback functions
to be called regularly.
The callback function handles different cases
of the memory status.
You can see here--
STATE_OK.
The green light means we can keep allocating.
STATE_APPROACHING_LIMIT, amber light,
means we should stop allocating.
And STATE_CRITICAL, red light, means
we should try to delegate memory.
Please use this link to try the new Memory Advice API
and check other AGDK libraries.
I am handing over to Bill to describe how to diagnose
low memory problems.
BILL BILODEAU: Thanks, Victor, for that great description
of the Memory Advice API.
I'm Bill Bilodeau, a Developer Relations engineer
working with Android Games.
In this part of the talk, I'll describe
how to diagnose and prevent low memory
problems using tools and APIs like the new Memory Advice API.
When faced with low memory problems,
it helps to understand how Android manages memory.
Android wants to use all of the memory,
since free memory is wasted space.
Memory not used by the game or the system
can be used for storing apps in the background.
If your game needs more memory, Android
will use the Low Memory Killer, or LMK,
to free memory on behalf of your game
by killing the background apps.
However, if the game tries to allocate too much memory,
the Low Memory Killer may need to kill the game in order
to leave space for the system.
It's not always obvious that a game stopped running
because of a low memory kill.
LMKs don't show up as crashes because they aren't actually
a crash.
The system is intentionally terminating the process
to save memory.
This means you won't see them as crashes in the Play Console
Vitals or in tools like Crashlytics.
One way you can tell for sure is to call
ApplicationExitInfo.getReason.
This was introduced in Android 11.
If LMK terminated the process, then it
will return REASON_LOW_MEMORY.
For earlier versions of Android, you'll
need to reproduce the LMK and use the ADB command
bugreport to get a log file from the device which
shows the LMK activity.
The Memory Advice API will warn the game
when memory is getting low.
If the game can reduce enough memory in response to this,
an LMK could be avoided.
However, in many cases, reducing a significant amount of memory
isn't possible for the game to do.
Another approach is to collect field data
about memory usage, which can be sent to the game's
backend server and used for setting a memory
budget for future versions of the game.
When the Memory Advice API returns critical,
you can save the amount of memory
the game is using to identify a high watermark.
Additionally, use the application exit info
to see if the game has been getting killed by the Low
Memory Killer to determine how severe the situation is.
Once you've determined that your game needs to reduce memory,
you need to find out where to reduce memory.
The Android Studio Memory Profiler
shows where malloc calls were made to allocate memory.
And it keeps track of where memory has been freed.
So not only can you tell where most of the memory
has been allocated, you can also tell
where memory has been leaked.
Since the Memory Profiler tracks malloc calls,
games that use custom allocators will
need to switch to use the system malloc and free calls.
For Unity games, the systemallocator command line
argument can be used to force Unity to use malloc and free.
Unreal has built-in support for Android Studio Memory
Profiler, which allows you to track the custom allocator
calls.
In the demo you're about to see, I'll
show how the Android Studio Memory Profiler can
be used to find memory leaks.
I'll be using a simple test app that allocates and frees
memory, similar to how a game would allocate memory
at the start of a level and free the memory
at the end of the level.
This is a very simple demo of how
a game might do allocations.
We have Android Studio connected to a Pixel 6.
Let's start the test on the device using the profiler.
When the test starts running, you
can see performance data from different parts of the device.
The Memory Profiler shows allocations broken down
by type.
Now we'll start a capture of native memory.
I'll simulate the start of a level
where the game will allocate memory.
You can see a sharp increase in total memory,
mostly from native allocations.
Now we'll simulate the end of the level
by freeing all of the memory that was allocated.
You can see some memory remaining.
Let's stop the recording and see what's going on.
The table shows allocation and de-allocation sizes.
Notice that about 150 megabytes was allocated,
but only about 123 megabytes was free.
Now we'll switch to the call stack view
so we can drill down to the actual malloc calls.
When we get down to the malloc calls,
we can see that there were four allocate objects calls--
one for A, B, C, and D. Notice that for the allocate objects
D, the D allocation size is 0.
This is the memory that's being leaked.
I can right-click on the call stack and select Jump to Source
to see where the allocation was made.
You can see the function calls malloc
and returns a pointer to the memory location.
Let's go up one level.
Select Jump to Source to see where this was called.
Notice that this functions assigns the memory location
of the global point of pMemD.
We can see that freeObjectsD should
be called to free the memory, but this function
is never called.
That is the cause of the leak.
I'll add that call to the freeObjects function
and rebuild the test.
Once again, the test is started from the profiler
and we can see the profiling traces.
I'll click on the memory section again.
Then I'll start a native recording.
This time, at the simulated start of the level,
I'll make repeated allocations.
Now I'll click to free the memory, simulating
freeing all of the memory that was allocated during the level.
Once again, not all of the memory was being freed.
In the Table view, we can see that 272 megabytes was
allocated, but only 166 megabytes was freed.
Let's switch to the Call Stack View
to drill down to see the native allocations.
This time, all of the allocation functions are leaking memory.
They're each allocating about 52 megabytes,
but only freeing about 26 megabytes.
Why is this happening?
Let's right-click on the calling function and select Jump
to Source.
We can see the malloc call for one
of the allocateObjects functions.
Now let's see how the allocateObjectsA function
is called.
It's used to assign the memory to the global pointer pMemA.
The problem is that this gets called multiple times.
And each time, it overwrites the global memory pointer
with new memory locations.
When the freeObjects functions are called,
they only free the most recent allocations.
The previous allocations are lost and never get freed.
In this demo, we showed two types of memory leaks
that we were able to find in Android Studio Native Memory
Profiler.
The first type was a simple missing free call,
and the second type was a more subtle lost reference
to memory.
Here are a few things to remember from this talk.
The new Memory Advice API should be
used to detect when your game is running low on memory.
It's a more accurate alternative to onTrim Memory,
and will work with older versions of Android.
You should respond to signals from the Memory
Advice API that indicate low memory situations.
If possible, reduce the amount of memory
that the game is using.
For games that can't reduce enough memory while running,
collect field data from devices that are
having trouble with low memory.
This data can later be used to adjust the game's memory
budget in future releases.
Finally, when you need to reduce the memory budget of a game,
you can use the Android Studio Memory Profiler
to see where memory is being allocated
and find possible memory leaks.
Thank you for joining us today.
We hope this gives you some insight
on diagnosing low memory problems in your games.
[UPBEAT MUSIC]