preface

This article is the first in a series of articles entitled “Understanding Android memory optimization in its entirety.” The main purpose of this series is to make a systematic summary, summary and study of the performance optimization part of Android development. This series of articles includes three parts: theoretical basis, tool use and project practice.

Theoretical basis: “A comprehensive understanding of Android memory optimization 1” -Android memory mechanism and management suggestions, mainly explains various basic knowledge involved in Android performance optimization

Tool usage: “Fully understand Android Memory optimization 2” – The use of memory optimization tools, mainly explains the use of various common tools for Android performance optimization.

Project practice: “Comprehensive Understanding of Android memory optimization 3” – from theory to practice, taking a real APP as an example, summarize the memory problems that will be ignored in the development.

The actual project address used in this article: github.com/linux-link/… You can read this article to learn about this project’s one-time componentization and Android Jetpack practices

This part belongs to the theoretical basis part of the three parts.

directory

  • Java memory allocation area
  • Java reference type
  • Java memory reclamation mechanism
  • Common memory leak scenarios
  • Tips and suggestions for memory management
  • conclusion

The body of the

Java memory allocation area

Java memory allocation mainly includes the following areas:

  1. Method area: stores the information of each class (including the name of the class, method information, field information), static variables, constants and the compiled code of the compiler, etc. This is an area shared by all threads.

  2. Virtual stack: Used to store local variables in a method (including non-static variables declared in a method and function parameters). For variables of primitive data types, its value is stored directly, and for variables of reference types, a reference to an object is stored. The size of a local variable table can be determined by the compiler, so the size of a local variable table does not change during program execution. Each thread has its own stack.

  3. Native method stack: a stack that stores native methods

  4. Heap: The largest area of memory allocation. The heap in Java is used to store objects themselves and arrays (array references, of course, are stored in the virtual machine stack). The difference is that in Java, programmers don’t have to worry about space release. Java’s garbage collection takes care of it automatically. Therefore, this area is also the main area managed by the Java garbage collector, and memory leaks occur in this area. In addition, the heap is shared by all threads, and there is only one heap in the JVM.

  5. Program counter: Multithreading is through the switch in turn to gain the CPU thread of execution time, therefore, in any one specific moment, a CPU kernel will only perform a thread in the instruction, in order to be able to make after each thread in the thread to restore before the switch position of program execution, each thread needs to have their own independent program counter, and cannot be interfere with each other, Otherwise, it will affect the normal execution order of the program. Therefore, it is safe to say that program counters are private to each thread. If you are interested in this area, please refer back to my previous article – An in-depth look at Android Multithreading (I) Java threading basics

Different articles have different allocation methods, but most of them are similar, and some articles also mention static fields and constant pools, both of which are part of the method area (as in JDK1.6). String constant pool in JDK1.7, stored in heap memory. After JDK1.8, the string constant pool is in local memory. Android programmers don’t really need to care which region it belongs to.

  1. Static field: part of the method area. “Static” here means “in a fixed position”. Static storage holds data that is always present while the program is running. You can use the keyword static to indicate that a particular element of an object is static, but JAVA objects themselves are never stored in static storage.

  2. Constant pool: part of the method area. Constant values are usually stored directly inside program code, which is safe because they can never be changed. Used to store string constants and primitive type constants.

Note: The memory allocation of strings in Java is special and requires extra attention. References to string objects are stored in the virtual machine stack. Those created at compile time (defined directly in double quotes) are stored in the constant pool, and those determined at run time (new) are stored in the heap. For strings equal to equals, there is always only one copy in the constant pool and multiple copies in the heap.

The above mentioned memory allocation area of the Java runtime, as Android programmers, we need to pay more attention to the stack and heap. But we might wonder, what’s the difference between a stack and a heap? Why do these two regions exist at the same time? With that in mind, let’s go back and look at these two regions.

The stack (stack)

The stack is located in universal RAM. Java has a virtual “stack pointer” that allocates new memory if it moves down. If it moves up, that memory is freed. This is a fast and efficient way to allocate memory, second only to registers.

This allocation of memory determines that when creating a program, the Java compiler must know the exact size and life cycle of all the data stored in the stack, because it must generate code to move the stack pointer up and down. In order to allocate memory quickly, the stack area limits the flexibility of the program, so this area only stores Java primitive data and references to objects and arrays. The objects themselves are stored in the heap or constant pool

Heap (heap)

The heap is also located in general-purpose RAM, where all Java objects are stored. The heap differs from the stack in that the compiler does not need to know how much storage area to allocate from the heap or how long the stored data lives in the heap.

Therefore, there is a great deal of flexibility in allocating storage in the heap. When you need to create an object, you just write a simple line of code called new, and when you execute this line of code, it automatically allocates memory in the heap. For this flexibility, storage allocation with the heap takes more time than memory allocation with the stack.

Java reference types

After the program is compiled and the Jvm has allocated memory for each object, Java’s garbage collection mechanism monitors the running status of each object in memory, including its application, reference, being referenced, and assignment. When an object is no longer referenced by a reference variable, the garbage collection mechanism reclaims it and frees up memory.

In Java, an object can be referenced by a local variable, a static variable of another class, or an instance variable of another object. When an object is referenced by a static variable, it is destroyed and reclaimed only if the class is destroyed. When an object is referenced by an instance variable of another object, it is destroyed and reclaimed only when the referenced object is destroyed or no longer referenced.

To better manage object references, the JDK provides four types of references: strong reference, soft reference, weak reference, and virtual reference. The following describes the reference methods and application scenarios

  • Strong reference

This is the default Java way of referring to objects, for example:

Object object=new Object();
Copy the code

Here object is a strong reference to the object, the strong reference to the Java object, even if the memory is insufficient will never be garbage collection mechanism.

  • Soft references

A SoftReference must be implemented using the SoftReference class, for example:

SoftReference<Object> object=new SoftReference<>();
Copy the code

Java objects referred to by weak references, like strong references, are not collected by the JVM’s garbage collection mechanism when there is enough memory, but are collected when the system is out of memory.

Soft references are very common in Android. For example, images obtained from the network are temporarily cached in the memory and can be directly retrieved from the memory when they are used again. Generally, soft references are set to prevent memory leaks.

  • A weak reference

A weak reference is similar to a soft reference, except that the object referenced by a weak reference has a shorter lifetime. WeakReference is realized by WeakReference class, for example:

Object object=new Object();
WeakReference<Object> wObject=new WeakReference<>(object);
Copy the code

For weakly referenced objects, when the JVM’s garbage collection mechanism runs, the memory used by the object is always reclaimed, regardless of whether there is enough memory.

  • Phantom reference

Soft and weak references can be used separately, but virtual references cannot be used separately. The main purpose of virtual references is to track the status of objects being garbage collected. The object referenced by a virtual reference doesn’t have much meaning in itself, the object doesn’t even feel the reference exists, and the get() method that uses a virtual reference is always empty. Such references are so rare in Android development that I won’t cover them too much.

Java garbage collection mechanism

A notable feature of the Java language is the introduction of garbage collection mechanism, which makes c++ programmers’ most troublesome memory management problem solved. It makes Java programmers no longer need to consider memory management when writing programs. Thanks to a garbage collection mechanism, objects in Java are no longer “scoped”; only references to objects are “scoped”. Garbage collection can effectively prevent memory leaks and efficiently use free memory.

Garbage collection has two main functions in Java:

  • 1. Track and monitor each Java object, and when an object loses its reference, reclaim its memory.

  • 2. Clear memory fragments generated during memory allocation or reclamation.

The amount of work that a garbage collection mechanism needs to do is not small, so the garbage collection algorithm becomes an important factor limiting the efficiency of Java programs. This is one of the main reasons why Android apps lag.

1. Garbage collection algorithm

In order to efficiently complete the work of memory collection, several different garbage collection algorithms are designed in Java:

  • Mark clearing algorithm

    The garbage collector accesses all reachable objects starting at the root, marks them as reachable, and then walks through the entire memory region again, reclaiming all unmarked objects.

    Advantages: No large-scale replication operation is required, and high memory utilization efficiency

    Disadvantages: the heap is traversed twice, which causes the application pause time to increase as the heap size increases, and garbage collection tends to be discontinuous, so there is a lot of debris in the collated heap.

  • Replication algorithm

    Divide the heap memory into two identical Spaces, access each associated reachable object from the root, copy the reachable object from space A to space B, and reclaim the entire space A.

    Advantages: For the replication algorithm, because it only needs to access all objects that have references, the entire memory space can be reclaimed after copying all objects that have references, completely ignoring those objects that do not have references, so the time cost of traversing space is relatively small.

    Disadvantages: waste half of memory, copying objects requires additional time costs.

  • Tag sorting algorithm

    The token-collation algorithm takes full advantage of the above two algorithms. The garbage collector accesses all reachable objects from the root and marks them as reachable. The garbage collector then moves the live objects together, a process known as memory compression, and the garbage collection mechanism reclaims the memory space occupied by unreachable objects, thus avoiding memory fragmentation caused by the collection.

As you can see from the above, no matter which memory collection algorithm is used, there are always mixed benefits and disadvantages. Therefore, there is always a combination of design approaches when implementing garbage collection. That is, different garbage collection implementations are used for different situations.

2. Reclaim memory generation by generation

Current garbage collectors use a generational approach to different collection designs. The basic idea of generational is to divide heap memory into three generations based on the lifetime of objects

  • Young Any non-static object that is first allocated memory space is assigned to the Young generation. Most objects in the Young generation quickly lose their references and become garbage objects, and only a small number of objects still have references when garbage collected. The garbage collector only needs to keep the referenced objects in the Young generation. A small number of objects can be copied at a small cost, which can give full play to the advantages of the replication algorithm. So, for the Young generation, the replication algorithm is mainly used to reclaim objects.

  • Old age

    If an object in the Young generation has not been collected after multiple garbage collections, the garbage collector moves the object to the Old generation. As the program continues to run, there are more and more objects in the Old generation, so the Old generation has more space than the Young generation. Old generation garbage collection has two characteristics: Old generation garbage collection does not need to be performed very often, because few objects die; Each garbage collection for the Old generation takes longer.

    Based on the above considerations, the Old generation mainly uses the tag collation algorithm. This calculation avoids copying a large number of objects from the Old generation, and since objects from the Old generation do not die quickly, the reclamation process does not generate a large amount of memory fragmentation

  • Permanent

    The Permanent generation was removed after JDK1.8, and garbage collectors usually don’t reclaim objects from the Permanent generation, so we won’t cover it here.

In summary, the memory of the Young generation will be reclaimed first, and a special reclamation algorithm (replication algorithm) will be used to reclaim the memory of the Young generation. For the Old generation, the recycling frequency is much lower, and the label collation algorithm is mainly used.

3.Android memory management mechanism

In the Android system, each APP has an independent main process, and the memory allocated by the system to each process is fixed before delivery. Phones with different brands, memory and systems are different. In general, the larger a phone’s factory memory, the greater the maximum amount of memory the system can allocate to each process.

As we all know, after Android 5.0, Google replaced Android with a more efficient virtual machine -ART. The early Dalvik VIRTUAL machine only had one memory reclamation algorithm, and the efficiency of memory reclamation was very low. ART virtual machine adopts a variety of different garbage collection algorithms according to the different situations when APP is running, so as to efficiently reclaim memory.

Android also has a Low Memory Killer mechanism for Memory reclamation. When the available Memory of the system is tight, this mechanism will globally check all running processes and kill those processes with lower weight based on the amount of Memory required and reclaim their Memory. In Android, processes are divided into foreground processes (interacting with the user), visible processes (not interacting with the user), server processes, background processes, and empty processes in descending order of weight.

In fact, after Android6.0, the management of memory is also more strict, for the user, the phone will be more smooth, not because of the lack of memory, and produce a variety of stop running. For developers, we don’t have to worry too much about running out of memory, but the downside is that most of the process survival measures used in development are already dead.

Common memory leak scenarios

1. What is memory leak

Memory leaks are also a hot topic in Android development and optimization. In learning to the development of Java application, we don’t have to be like C/C + + manual release the memory object to occupy, the JVM garbage collector automatically recycling useless objects of memory space, this will give a person a kind of illusion, Java will not have problems with memory leaks, but improper use in Java development, as there is memory leak.

First we need to take a quick look at what a memory leak is.

During the running of the program, memory space will be allocated for objects, variables, arrays, etc. When these allocated memory space is no longer used, the garbage collector reclaims their memory in time to ensure that the memory area can be used again. But a memory leak occurs when the unused memory space can neither be reclaimed nor used by new objects. Over time, the system runs out of available memory until there is no available memory, and an OutOfMemory exception occurs in Android.

2. Common memory leakage scenarios

Do you have a question here, why can’t the memory space that is no longer being used be reclaimed? The reason we mentioned in Java’s garbage collection mechanism is that if an object is still held by an external reference at the time of garbage collection, the garbage collector cannot reclaim the memory occupied by the object, even if the external reference is never used again.

Here are some common examples of memory leaks on Android:

  • Objects that need to be reclaimed are held by static variables

A typical example is when we pass in a singleton Context, we pass in the current Activity Context

 class Example {
    private static volatile Example ourInstance;

    private Context mContext;

    static Example getInstance(Context context) {
        if (ourInstance == null) {
            synchronized (Example.class) {
                if(ourInstance == null) { ourInstance = new Example(context); }}}returnourInstance; } private Example(Context context) { mContext = context; }}Copy the code

If we pass in the Activity Context in our code, the memory occupied by the Activity will not be recycled during the app run cycle. For more details, see below.

We can replace the Context here with the Application Context, because the lifecycle of the Application is the lifecycle of the App.

private Example(Context context) {
        mContext = context.getApplicationContext();
}
Copy the code
  • Non-static inner classes hold references to outer classes

In Java, inner classes implicitly hold references to external classes, which in general does not cause memory leaks, but it can if time-consuming operations are performed in inner classes.

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
    
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() { try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); }}}); thread.start(); }Copy the code

In the code above, Thread implicitly holds a reference to the Activity class. When the Activity exits, Thread is still executing in the background, and the Activity cannot be reclaimed because it is held by background threads.

The above example only uses Thread as an example of time-consuming operations. Problems such as AsyncTask and Handler exist. However, after the execution of time-consuming operations, the Thread is released normally, and the Activity can be reclaimed normally. This practice of using internal classes to perform time-consuming operations in an Activity is inherently wrong and can lead to other exceptions. It is not recommended.

If you must write it this way, you can write it as follows:

  static class MyThread extends Thread {

        private SoftReference<Activity> mActivity;

        public MyThread(Activity activity) {
            mActivity = new SoftReference<>(activity);
        }

        @Override
        public void run() { super.run(); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

Change the inner class to a static inner class. The static inner class does not hold a reference to an external class and needs to be passed in by itself. In this case, we set the reference to the external class as a soft reference. The Activity should then be destroyed normally. (Note that the example above is a thread executing in the background, and even if the Activity is reclaimed, the thread itself is not reclaimed.)

  • The resource object is not closed

Close it when using IO, File streams, or RESOURCES such as Sqlite and Cursor. These resources usually use buffers for read and write operations. If they are not closed in time, these buffer objects will always be occupied and cannot be released, resulting in memory leaks. So we turn them off when they are no longer needed, so that the buffer can be released in time to avoid memory leaks.

  • Property animation causes a memory leak

Animation can also be a time-consuming task. For example, ObjectAnimator is started in an Activity, but the cancle method is not called when it is destroyed. Although we can no longer see the animation, the animation will continue to play. The control referenced the Activity, which causes the Activity to not be released properly. Therefore, cancel the property animation when the Activity is destroyed to avoid memory leaks

In Android and even Java, there are many places that cause memory leaks due to improper code writing. I will not enumerate them here, but I will explain how to detect memory leaks and restore the causes of memory leaks step by step in future articles.

Five, memory management skills and suggestions

Following are a few tips for Java memory management based on the memory reclamation mechanism described earlier.

  • Use direct quantities whenever possible

When wrapping instances of classes with strings and Byte, Short, Integer, Long, Float, Double, Boolean, Character, objects should not be created as new, but as direct quantities

For example: use

String STR = "hello"Copy the code

Rather than

String STR =new String (" hello ");Copy the code

With direct quantities, the Jvm’s String cache pool caches this String, but with new, not only does the Jvm need to cache this String, but the underlying String referenced by STR contains an array of type CHAR, resulting in unnecessary memory waste.

Char [] c = {' h ', 'e', 'l','l'.'o'};
Copy the code
  • Use StringBuider and StringBuffer to concatenate strings

When we learn about strings, we know that strings are immutable, but such a program will not report an error

  String s1 = "hello";
  s1 = s1 + " world";
Copy the code

This is because Java generates a large number of temporary strings when concatenating strings with String objects, and these strings consume the appropriate memory. Using StringBuider and StringBuffer as variable length string objects doesn’t have this problem.

  • Release unwanted object references early

In general, objects referenced by local variables in a method have a short lifetime, and there is no need to explicitly set the object to NULL, except in some cases such as

BeanNews news = new BeanNews(); // Some general operations... news = null; // Time consuming, memory consuming operation......Copy the code

Setting the local object to NULL makes it possible to release the memory occupied by the local object as soon as possible when there are time-consuming or memory consuming operations following the local object. Why is it possible? Because garbage collection is determined by the Jvm, the developer cannot decide when to do it.

  • Avoid creating Java objects in loops or frequently called methods

Such as:

for(int i = 0; i <100 ; i++) { BeanNews news=new BeanNews(); ... }Copy the code

Although news is a local variable, and the memory it occupies will be reclaimed after the loop ends, it is also necessary to frequently allocate memory space for the reference variable news to perform initialization operations during the loop. In the process of continuous allocation and reclamation, the performance of the program will also be affected.

The following optimizations can be made:

BeanNews news;for(int i = 0; i <100 ; i++) { news=new BeanNews(); ... }Copy the code

This eliminates the need to frequently allocate memory and perform initialization for a variable of type news.

  • Cache frequently used objects

For frequently used objects, we can consider storing them in the cache pool so that they can be used next time without creating or initializing them here. During Android development, we often use various collection classes as cache containers,

... List<BeanNews> news = new ArrayList<>(); BeanNews beanNews = new BeanNews(); ... news.add(beanNews); ... BeanNews beanNews1 = news.get(0);Copy the code

It should be noted that cache is a typical space sacrifice for time, so we should pay attention to not let the cache container occupy too much memory space. For large data caches, a series of elimination algorithms are used to control the memory size occupied by the cache to a reasonable range.

  • Try not to use finalize methods

When an object uses a reference, the garbage collection mechanism will call the Finalize method to clean up resources before the garbage collector collects the object.

Therefore, some developers will consider using Finalize to clean up resources. However, garbage collection is already a big effort, especially when the Young generation memory is reclaimed, which can cause the program to pause. When garbage collection has already restricted the running efficiency of the program, using Finalize to clean up resources will put more burden on garbage collector and result in worse running efficiency of the program.

  • Using a SoftReference

Soft references should be considered when creating large arrays or when creating an object that takes up a lot of memory but is not very important. Objects that use soft references voluntarily “sacrifice” themselves when memory is tight, freeing up valuable space for later objects.

However, because of the uncertainty of the soft reference, when fetching the object referenced by the soft reference, we need to check whether it is null. If it is null, you need to try to rebuild it.

  • Be careful with static variables

A variable that is static has a lifetime consistent with its class. If the class is not unloaded, then the static variable itself is not destroyed.

class BeanNews{
    static Object obj=new Object();
}
Copy the code

In the example above, Object is referenced by the static variable obj, and the memory it occupies in the heap can never be reclaimed until the program ends.

We mentioned above that the Java memory allocation area has a method area where static variables and class information are stored.

Six, summarized

Here, for Android memory optimization need to understand the theoretical basis on a brief introduction to the end, master the theoretical basis, on the one hand, we can avoid making some low-level mistakes, improve the quality of the code, on the other hand, in the future do memory optimization will become more skillful, perhaps will not produce “oh my god, Why is there a memory leak? The next article will introduce the use of common tools for Android memory optimization. “Android Memory optimization 2” – the use of memory optimization tools, thank you for reading!