2. 2
Agenda
• How Android Manages Memory
• How your App Should Manage
Memory
• Performance Tips
3. 3
How Android Manages
Memory
• Android uses paging and memory-
mapping (mmapped) to manage
memory.
• Android does not offer swap space for
memory.
• However, there is one exception any
files mmapped in without
modification, such as code, can be
paged out of RAM if the system wants to
use that memory elsewhere
4. 4
Sharing Memory
In order to fit everything it needs in RAM, Android tries to share RAM pages
across processes. It can do so in the following ways:
1. Each app process is forked from an existing process
called Zygote. The Zygote process starts when the
system boots and loads common framework code and
resources . To start a new app process, the system forks
the Zygote process then loads and runs the app's code in
the new process1.
2. Most static data is mmapped into a process.
3. In many places, Android shares the same dynamic RAM
across processes using explicitly allocated shared
memory regions2
1- using copy-on-write semantics
2- gralloc buffers for UI
5. 5
Allocating and Reclaiming App
Memory
Here are some facts about how Android allocates then reclaims memory from
your app:
• The Dalvik heap for each process is
constrained to a single virtual memory
range. This defines the logical heap
size, which can grow as it needs to (but
only up to a limit that the system
defines for each app).
6. 6
Allocating and Reclaiming App
Memory
• The logical size of the heap is not the
same as the amount of physical
memory used by the heap. When
inspecting your app's heap, Android
computes a value called the
Proportional Set Size (PSS), which
accounts for both dirty and clean pages
that are shared with other processes.
• This (PSS) total is what the system
considers to be your physical memory
footprint.
7. 7
Allocating and Reclaiming App
Memory
• The Dalvik heap does not compact the
logical size of the heap, meaning that
Android does not defragment the heap to
close up space. Android can only shrink
the logical heap size when there is
unused space at the end of the heap.
• But this doesn't mean the physical
memory used by the heap can't shrink.
After garbage collection, Dalvik walks
the heap and finds unused pages, then
returns those pages to the kernel
8. 8
Restricting App Memory
• To maintain a functional multi-tasking
environment, Android sets a hard limit
on the heap size for each app. The
exact heap size limit varies between
devices based on how much RAM the
device has available overall.
• If your app has reached the heap
capacity and tries to allocate more
memory, it will receive
an OutOfMemoryError.
• You might want to query the system to determine exactly how much
heap space you have available on the current device—for example, to
determine how much data is safe to keep in a cache. You can query
the system for this figure by calling getMemoryClass(). This returns
an integer indicating the number of megabytes available for your
app's heap.
9. 9
Switching Apps
• Instead of using swap space when the
user switches between apps, Android
keeps processes that are not hosting
a foreground ("user visible") app
component in a least-recently used
(LRU) cache.
• For example, when the user first
launches an app, a process is created
for it, but when the user leaves the
app, that process does not quit. The
system keeps the process cached, so if
the user later returns to the app, the
process is reused for faster app
switching.
10. 10
Switching Apps
• If your app has a cached process and
it retains memory that it currently does
not need, then your app—even while
the user is not using it—is constraining
the system's overall performance.
• So, as the system runs low on
memory, it may kill processes in the
LRU cache beginning with the process
least recently used, but also giving
some consideration toward which
processes are most memory
intensive.
11. 11
Agenda
• How Android Manages Memory
• How your App Should Manage
Memory
• Performance Tips
12. 12
How your App should manage data
• You should consider RAM constraints
throughout all phases of
development, including during app
design (before you begin
development). There are many ways
you can design and write code that lead
to more efficient results, through
aggregation of the same techniques
applied over and over.
• You should apply the following
techniques while designing and
implementing your app to make it more
memory efficient.
13. 13
1. Use services sparingly
• If your app needs a service to perform
work in the background, do not keep it
running unless it's actively performing
a job. Also be careful to never leak your
service by failing to stop it when its
work is done.
• When you start a service, the system prefers to always keep the
process for that service running. This makes the process very
expensive because the RAM used by the service can’t be used by
anything else or paged out. This reduces the number of cached
processes that the system can keep in the LRU cache, making app
switching less efficient
14. 14
1. Use services sparingly
• So how can we avoid this problem?
• The best way to limit the lifespan of
your service is to use
an IntentService, which finishes itself
as soon as it's done handling the
intent that started it.
• Leaving a service running when it’s not needed is one of the
worst memory-management mistakes an Android app can make.
So don’t be greedy by keeping a service for your app running. Not
only will it increase the risk of your app performing poorly due to
RAM constraints, but users will discover such misbehaving apps and
uninstall them.
15. 15
2. Release memory when your user
interface becomes hidden
• When the user navigates to a different
app and your UI is no longer
visible, you should release any
resources that are used by only your
UI. Releasing UI resources at this time
can significantly increase the system's
capacity for cached processes, which
has a direct impact on the quality of the
user experience.
• Give me an example of a UI resource?
16. 16
2. Release memory when your user
interface becomes hidden
• How can we do this?
• To be notified when the user exits your
UI, implement
the onTrimMemory() callback in
your Activity classes. You should use
this method to listen for the
TRIM_MEMORY_UI_HIDDEN
level, which indicates your UI is now
hidden from view and you should free
resources that only your UI uses.
17. 17
2. Release memory when your user
interface becomes hidden
• Note: your app receives the onTrimMemory() callback
with TRIM_MEMORY_UI_HIDDEN only when all the UI components of your
app process become hidden from the user. This is distinct from
the onStop() callback, which is called when an Activity instance becomes
hidden, which occurs even when the user moves to another activity in
your app.
• So although you should implement onStop() to release activity resources
such as a network connection or to unregister broadcast receivers, you
usually should not release your UI resources until you
receive onTrimMemory(TRIM_MEMORY_UI_HIDDEN).
• This ensures that if the user navigates back from another activity in your
app, your UI resources are still available to resume the activity quickly.
18. 18
3. Release memory as memory
becomes tight
• During any stage of your app's lifecycle, the onTrimMemory()callback also
tells you when the overall device memory is getting low. You should
respond by further releasing resources based on the following memory
levels delivered by onTrimMemory():
• TRIM_MEMORY_RUNNING_MODERATE
• Your app is running and not considered killable, but the device is
running low on memory and the system is actively killing processes
in the LRU cache.
• TRIM_MEMORY_RUNNING_LOW
• Your app is running and not considered killable, but the device is
running much lower on memory so you should release unused
resources to improve system performance (which directly impacts your
app's performance).
• TRIM_MEMORY_RUNNING_CRITICAL
• Your app is still running, but the system has already killed most of
the processes in the LRU cache, so you should release all non-
critical resources now. If the system cannot reclaim sufficient amounts
of RAM, it will clear all of the LRU cache and begin killing processes
that the system prefers to keep alive, such as those hosting a running
service.
19. 19
3. Release memory as memory
becomes tight
• The previous events was given when your app is alive, however there are
different set of levels that you can receive when your app is cached.
• TRIM_MEMORY_BACKGROUND
• The system is running low on memory and your process is near the
beginning of the LRU list. Although your app process is not at a high
risk of being killed, the system may already be killing processes in the
LRU cache. You should release resources that are easy to recover
so your process will remain in the list and resume quickly when the user
returns to your app.
• TRIM_MEMORY_MODERATE
• The system is running low on memory and your process is near the
middle of the LRU list. If the system becomes further constrained for
memory, there's a chance your process will be killed.
• TRIM_MEMORY_COMPLETE
• The system is running low on memory and your process is one of the
first to be killed if the system does not recover memory now. You
should release everything that's not critical to resuming your app
state.
20. 20
4. Check how much memory you
should use
• As mentioned earlier, each Android-powered device has a different
amount of RAM available to the system and thus provides a different heap
limit for each app. You can call getMemoryClass()to get an estimate of
your app's available heap in megabytes. If your app tries to allocate more
memory than is available here, it will receive an OutOfMemoryError.
• In very special situations, you can
request a larger heap size by setting
the largeHeap attribute to "true" in the
manifest <application> tag.
• If you do so, you can
call getLargeMemoryClass() to get an
estimate of the large heap size.
21. 21
4. Check how much memory you
should use
• However, the ability to request a large heap is intended only for a small
set of apps that can justify the need to consume more RAM.
• Think of an example?
• Never request a large heap simply because you've run out of
memory and you need a quick fix—you should use it only when you
know exactly where all your memory is being allocated and why it must
be retained. Yet, even when you're confident your app can justify the
large heap, you should avoid requesting it to whatever extent possible.
Using the extra memory will increasingly be to the detriment of the
overall user experience because garbage collection will take longer and
system performance may be slower when task switching or performing
other common operations.
22. 22
5. Avoid wasting memory with
bitmaps
• When you load a bitmap, keep it
in RAM only at the resolution you
need for the current device's
screen, scaling it down if the
original bitmap is a higher
resolution.
23. 23
6. Use optimized data containers
• Take advantage of optimized containers in the Android
framework, such
as SparseArray,SparseBooleanArray, and LongSparseArray.
• These collections are used to map objects, you can compare this to
Hashmap.
• Advantages:
• Avoid using extra entry object for each mapping.
• Avoid autoboxing for keys and sometimes for values.
• Disadvantages:
• Use binary search for lookups, instead of hashing. Slower for
large number of entries , > 1000, compared to Hashmaps.
24. 24
7. Be aware of memory overhead
• Be knowledgeable about the cost and overhead of the language and
libraries you are using, and keep this information in mind when you
design your app, from start to finish.
• Enums often require more than twice as much memory as
static constants. You should strictly avoid using enums on
Android.
• Every class in Java (including anonymous inner classes) uses
about 500 bytes of code.
• Every class instance has 12-16 bytes of RAM overhead.
• Putting a single entry into a HashMap requires the allocation of
an additional entry object that takes 32 bytes
25. 25
8. Be careful with code abstractions
• Often, developers use abstractions simply as a "good programming
practice," because abstractions can improve code flexibility and
maintenance.
• However, abstractions come at a significant cost:
• Generally they require a fair amount more code that needs to be
executed, requiring more time and more RAM for that code to
be mapped into memory.
• So if your abstractions aren't supplying a significant
benefit, you should avoid them.
26. 26
9. Avoid dependency injection
frameworks
• Using a dependency injection framework such
as Guice or RoboGuice may be attractive because they can
simplify the code you write and provide an adaptive environment
that's useful for testing and other configuration changes.
• However, these frameworks tend to perform a lot of process
initialization by scanning your code for annotations, which can
require significant amounts of your code to be mapped into RAM
even though you don't need it
27. 27
10.Be careful about using external
libraries
• External library code is often not written for mobile environments and
can be inefficient when used for work on a mobile client.
• At the very least, when you decide to use an external
library, you should assume you are taking on a significant
porting and maintenance burden to optimize the library for
mobile.
28. 28
11.Use ProGuard to strip out any
unneeded code
• The ProGuard tool shrinks, optimizes, and obfuscates your code by
removing unused code and renaming classes, fields, and methods
with semantically obscure names.
• Using ProGuard can make your code more compact, requiring fewer
RAM pages to be mapped.
29. 29
Agenda
• How Android Manages Memory
• How your App Should Manage
Memory
• Performance Tips
30. 30
Performance Tips
• This part primarily covers micro-optimizations that can improve
overall app performance when combined.
• but it's unlikely that these changes will result in dramatic
performance effects. Choosing the right algorithms and data
structures should always be your priority, but is outside the scope of
this document.
• There are two basic rules for writing efficient code:
• Don't do work that you don't need to do.
• Don't allocate memory if you can avoid it.
31. 31
1. Avoid Creating Unnecessary
Objects
• Object creation is never free.
• As you allocate more objects in your app, you
will force a periodic garbage
collection, creating little "hiccups" in the user
experience.
• Thus, you should avoid creating object
instances you don't need to.
• Any Ideas on how we can do this??
32. 32
1. Avoid Creating Unnecessary
Objects
• If you have a method returning a string, and
you know that its result will always be
appended to a StringBuffer anyway, change
your signature and implementation so that the
function does the append directly, instead of
creating a short-lived temporary object.
• When extracting strings from a set of input
data, try to return a substring of the original
data, instead of creating a copy. You will create
a new String object, but it will share
the char[] with the data.
33. 33
2. Prefer Static Over Virtual
• If you don't need to access an object's
fields, make your method static.
• Invocations will be about 15%-20% faster.
• It's also good practice, because you can tell
from the method signature that calling the
method can't alter the object's state.
34. 34
3. Use Static Final For Constants
• Consider the following declaration at the top of
a class:
• static int intVal = 42;
• static String strVal = "Hello, world!";
• What do you think is happening? And how
beneficial is the alternative if there is any?
35. 35
4. Avoid Internal Getters/Setters
• In native languages like C++ it's common practice to use getters (i =
getCount()) instead of accessing the field directly (i = mCount).
• However, this is a bad idea on Android. Virtual method calls are
expensive, much more so than instance field lookups. It's reasonable to
follow common object-oriented programming practices and have
getters and setters in the public interface, but within a class you
should always access fields directly.
36. 36
5. Use Enhanced For Loop Syntax
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}
• What do you think the problem here?
37. 37
5. Use Enhanced For Loop Syntax
public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum +=
localArray[i].mSplat;
}
}
public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
• What do you think about
these options?
38. 38
5. Use Enhanced For Loop Syntax
• zero() is slowest, because the JIT can't yet optimize away the cost of
getting the array length once for every iteration through the loop.
• one() is faster. It pulls everything out into local variables, avoiding the
lookups. Only the array length offers a performance benefit.
• two() is fastest for devices without a JIT, and indistinguishable
from one() for devices with a JIT. It uses the enhanced for loop syntax
introduced in version 1.5 of the Java programming language.
• So, you should use the enhanced for loop by default, but consider a
hand-written counted loop for performance-
critical ArrayList iteration.
39. 39
6. Avoid Using Floating-Point
• As a rule of thumb, floating-point is about 2x slower than integer on
Android-powered devices.
• In speed terms, there's no difference between float and double on
the more modern hardware. Space-wise, double is 2x larger.
• As with desktop machines, assuming space isn't an issue, you
should prefer double to float.