Basic knowledge of

A brief introduction to Java memory allocation

  • Non-heap: Allocated at compile time and present for the entire duration of the program. It mainly stores static data and constants;
  • Stack area: when a method is executed, local variables inside the method body will be created in the stack area memory, and the memory will be automatically released after the method ends.
  • Heap: Usually used to hold new objects. The GC is responsible for the collection.

Java has four different reference types

  • Strong References: The JVM would rather throw OOM than let GC reclaim objects with Strong references.
  • Soft Reference:An object only has soft references, when memory is lowBefore the object is collected by the GC.
  • Weak Reference:During GC, if an object has only weak references, it will be reclaimed.
  • Phantom Reference: Can be collected by the GC at any time. When the garbage collector is about to reclaim an object and finds that it has a Phantom Reference, it adds the Phantom Reference to its associated Reference queue before reclaiming the object’s memory. A program can determine whether an object is about to be reclaimed by determining whether a virtual reference to the object exists in the reference queue. Can be used as a flag for GC to reclaim an Object.

Difference with Android: In versions later than 2.3, Android retrieves SoftReference objects in advance, even if the memory is sufficient. The other items are the same as those in Java.

So Google officially recommends using LruCache(least recentlly use algorithm). Memory will be controlled within a certain size, beyond the maximum will be automatically reclaimed, the developer set the maximum.

What is a memory leak?

  • In C++, a memory leak is a new object without a delete.
  • In the case of Java, objects stored in the heap cannot be properly collected by GC.

Root cause of memory leak

Long-life objects hold strong/soft references to short-life objects, causing short-life objects that should be recycled to fail to be recycled properly.

For example, in the singleton pattern, we often need to pass a Context when we get a singleton. A singleton is a long-life object (it terminates at the end of the application), and if we pass an Activity as a context, the Activity cannot be destroyed because the reference is held, resulting in a memory leak.

Hazards of memory leaks

  • Performance issues: When Android is running, memory leaks will result in less available memory for other components. On the one hand, GC frequency will increase. When GC occurs, all processes have to wait. On the other hand, less memory may cause the system to allocate some extra memory to you, affecting the overall system health.
  • Run Crash problem: Memory leak is one of the most important causes of out of memory (OOM), leading to Crash. If the application consumes all available heap space and attempts to allocate new objects on the heap raises an OOM(Out Of Memory Error) exception, the application crashes and exits.

A classic case of a memory leak

Perpetual singletons

Because of the static nature of the singleton pattern, its lifetime is as long as that of our application, and accidentally letting a singleton hold strong references to an Activity indefinitely can cause a memory leak.

The solution

  • Change the Context passed in to an Application Context that is as long as the Application life cycle.
  • By rewriting the Application to provide the getContext method, you do not need to pass in the context when fetching a singleton.
public class BaseApplication extends Application{
    private static ApplicationContext sContext;
    @Override
    public void onCreate(){
        super.onCreate();
        sContext = getApplicationContext();
    }
    public static Context getApplicationContext() {returnsContext; }}Copy the code

Memory leak caused by Handler

Handler is a TLS (Thread Local Storage) variable, so its lifecycle is inconsistent with that of an Activity. Therefore, updating a UI through a Handler is difficult to keep consistent with the View or Activity lifecycle, and can easily result in incorrect release.

Such as:

public class HandlerBadActivity extends AppCompatActivity {
    private final Handler handler = new Handler@override public void handleMessage(Message MSG){super.handleMessage(MSG); }}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler_bad); // Send a message handler.postDelayed(new)Runnable() {// Internally encapsulates the Runable as a Message object and assigns message. target to handler@override public voidrun() {
                //dosomething } }, 1000 * 60 * 5); this.finish(); }}Copy the code

This code sends a Message that is delayed for 5 minutes. When the Activity exits, the Message is still waiting in the MessageQueue of the main thread. The Message at this point holds a strong reference to Handler (specified by message.target when created), and since Handler is a non-static inner class of HandlerBadActivity, So Handler will hold a strong reference to HandlerBadActivity, so even if HandlerBadActivity calls Finish, it will not be able to reclaim memory, causing a memory leak.

The solution

Declare Handler as a static inner class, but note that ** if you use non-static objects from external classes such as Context, you should still use ApplicationContext or hold these external objects by weak references **.

public class HandlerGoodActivity extends AppCompatActivity { private static final class MyHandler extends Handler{// Declare static internal class (avoid holding strong references to external classes) private Final WeakReference<HandlerGoodActivity> mActivity; public MyHandler(HandlerGoodActivity activity){ this.mActivity = new WeakReference<HandlerGoodActivity>(activity); } @override public void handleMessage(Message MSG) {HandlerGoodActivity activity = mactivity.get ();if(activity = = null | | activity. IsFinishing () | | activity. IsDestroyed ()) {/ / whether the activity is empty, And whether it is being destroyed, or has been destroyed removeCallbacksAndMessages (null);return; } / /do something
        }
    }
    private final MyHandler myHandler = new MyHandler(this);
}
Copy the code

Be careful with static member variables

Static modifiers are in the method area of memory and have the same life cycle as the App. This inevitably leads to a number of problems. If your app processes are designed to stay in memory, that memory will not be released even if your app cuts to the background.

The solution

Do not initialize static members at class initialization, that is, consider lazy loading. Architectural design should consider whether this is really necessary, and try to avoid. If the architecture needs to be designed this way, then it is your responsibility to manage the life cycle of this object.

The context of an Application, a Service, or an Activity can be used only when the context of an Application is used.

function Application Service Activity
Start an Activity NO1 NO1 YES
Show a Dialog NO NO YES
Layout Inflation YES YES YES
Start an Service YES YES YES
Bind an Service YES YES YES
Send a Broadcast YES YES YES
Register BroadcastReceiver YES YES YES
Load Resource Values YES YES YES
  • NO1 indicates that Application and Service can start an Activity, howeverA new task queue needs to be created.
  • For a Dialog, it can only be created in an Activity.

Memory leaks caused by using system services

To make it easier to use some common system services, the Activity is packaged. For example, the getPackageManager can get the PackageManagerService in Activtiy, but, It actually calls the getPackageManager method of the Activity’s corresponding ContextImpl

ContextWrapper#getPackageManager

@Override
public PackageManager getPackageManager() {
    return mBase.getPackageManager();
}
Copy the code

ContextImpl#getPackageManager

@Override
public PackageManager getPackageManager() {
    if(mPackageManager ! = null) {return mPackageManager;
    }
    IPackageManager pm = ActivityThread.getPackageManager();
    if(pm ! = null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); / / create ApplicationPackageManager} return null; }Copy the code

ApplicationPackageManager#ApplicationPackageManager

ApplicationPackageManager(ContextImpl context, IPackageManager pm) { mContext = context; // Save ContextImpl strong reference mPM = PM; } private UserManagerService(Context context, PackageManagerService pm, Object packagesLock, File dataDir) { mContext = context; // Hold the external Context reference mPm = PM; // code omitted}Copy the code

PackageManagerService#PackageManagerService

public class PackageManagerService extends IPackageManager.Stub { static UserManagerService sUserManager; Public PackageManagerService(Context Context, Installer Installer, Boolean factoryTest, boolean onlyCore) { sUserManager = new UserManagerService(context, this, mPackages); // Initialize UMS}}Copy the code

If the Packagemanger method is null in ContextImpl, the PMS is ContextImpl. If the PackageManager is null in ContextImpl, It will create a PackageManger (ContextImpl will pass itself in, and ContextImpl mOuterContext will be the Activity), Creating a PackageManager actually creates the PackageManagerService (PMS), The constructor of the PMS creates a UserManger (when initialized, the UserManger holds a strong reference to ContextImpl).

As long as the CLASS of the PMS is not destroyed, the UserManger will be referred to and the resources associated with it will not be released properly.

The solution

Change getPackageManager() to getApplication()#getPackageManager(). This refers to the Application Context instead of the Activity.

Stay away from non-static inner classes and anonymous classes

Because using non-static inner classes and anonymous classes both hold references to external classes by default, this can lead to memory leaks if the life cycles are inconsistent.

Public class NestedClassLeakActivity extends AppCompatActivity {class InnerClass {// Non-static InnerClass} private static InnerClass sInner; @override protected void onCreate(Bundle savedInstanceState) {super.oncreate (savedInstanceState);setContentView(R.layout.activity_nested_class);
        if(sInner == null) { sInner = new InnerClass(); // Create instance of non-static inner class}}}Copy the code

By default, a non-static inner class holds a reference to an external class, and the external class has a static instance of the non-static inner class. The lifetime of the static instance is the same as that of the application, and the static instance holds a reference to the Activity. As a result, the Activity’s memory resources cannot be reclaimed properly.

The solution

Making the inner class static can also extract the inner class and encapsulate it as a singleton

Memory leak caused by collection

We often add references to objects in the collection container (such as ArrayList), and when we no longer need the object (usually by calling the remove method), we do not remove its references from the collection (one such case is when the remove method does not assign null to references that are no longer needed). Take the remove method of ArrayList as an example

Public E remove(int index) {// Check RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; Int numMoved = size-index - 1; // Array copy, which moves the element after index one position forwardif(numMoved > 0) System. arraycopy(elementData, index+1, elementData, index, numMoved); ElementData [--size] = null; // Empty the last element of the array (the last element is useless because one element is removed and all elements after index are moved forward) so that gc can reclaim elementData[--size] = null as soon as possible; // Let gcdo its work
    return oldValue;
}
Copy the code

Memory leak caused by WebView

When a WebView parses a web page, it applies for Native heap memory to save page elements. When a page is complex, it will occupy a large amount of memory. If the page contains images, the footprint is even worse. In addition, when a new page is opened, the memory occupied by the previous page is not released for fast rollback. Sometimes browsing more than a dozen web pages will take up hundreds of megabytes of memory. If too many web pages are loaded, the system will be overwhelmed and the application will be forcibly shut down, that is, the application will blink or restart.

Since Native Heap memory is used, the actual memory occupied is not displayed in the DDMS Heap tool (the DMS Heap tool only shows the memory allocated by the Java VIRTUAL machine, even though Native Heap memory is hundreds of megabytes, this is only a few megabytes or tens of megabytes). Native heap memory information can only be seen using some of the adb shell commands such as dumpsys meminfo package name, or debug.getnativeHeapsize () in the program.

Due to a BUG in WebView, this memory is not released even after the Activity(or Service) it is in ends (onDestroy()), or after webView.destroy () is called directly.

The solution

Put activities (or services) that use WebView in a separate process.

  • If the system detects that an application occupies too much memory, it is likely to be killed
  • You can also Kill the process by calling System.exit(0) after the Activity(or Service) in which it is running ends. The system allocates memory based on processes. After a process is shut down, the system automatically reclaims all memory.

An Activity using a WebView calls webView.onpause ()== and == webView.deStory () when the page exits onDestory at the end of its life cycle to cause the system to release webView-related resources.

Other common causes of memory leaks

  • Under Android 3.0, Bitmap does not use recycle() to free memory when not in use.
  • A static instance of a non-static inner classProne to memory leaks: If you don’t have control over the life cycle of a class’s inner classes (such as special handlers in an Activity, etc.), try to use static classes and weak references (such as ViewRoot’s implementation).
  • Be alert for memory leaks caused by unterminated threads; For example, if a Thread is associated with an Activity that has a lifetime longer than the Activity, remember to terminate the Thread when exiting the Activity.

    A classic example is the Run method of HandlerThread. This method is an infinite loop that does not end on its own. The life of the thread exceeds the Activity life cycle, and we must manually call Thread.getlooper ().quit() in the Activity’s destruction method to keep it from leaking.

  • Object registration and unregistration do not come togetherMemory leakage caused by; Such as registering broadcast receivers, registering observers (typically such as database listeners), and so on.
  • Create and close leaks that do not occur in pairs; For example, Cursor resources must be closed manually, WebViews must be destroyed manually, and streams and other objects must be closed manually.
  • Avoid memory leaks caused by code design pattern errors; For example, A circular reference, A holds B, B holds C, C holds A, such A design is not free.