1. Memory jitter

A large number of objects are created in a short time, occupying the Eden area, leading to frequent MinorGC and memory jitter.

Phenomenon of 1.1.

The MemoryProfile image is zigzagged and accompanied by a large number of white trash cans.

A common problem is creating a large number of objects in the smallest loop

String s = "";
for (int i = 0; i < 10000; i++) {
    s += "," + i;  // Create a large number of string constant objects
}
Copy the code

1.2. Avoid

There are ways to avoid these problems:

  • Try to avoid creating objects inside the loop and move object creation outside the loop.
  • Avoid custom viewsonMeasure,onLayout,onDrawAnd so oninvalidate()Method to inform the main thread that the current screen has expired and redraw it when the thread is idle, and do not create a large number of objects.
  • When needed in large quantitiesBitmapTry to cache them in an array for reuse.
  • For objects that can be reused, you can also cache them using object pools.
  • String concatenation insteadStringBufferorStringBuilderTo execute.

2. Memory leaks

Long-life objects hold references to short-life objects, causing the allocated memory space not to be reclaimed in a timely manner, resulting in less and less usable memory.

2.1. Detection and location of memory leaks

Memory Profiler

After capturing heap dumps using Memory Profiler, filter out our own objects.

After exiting LeakActivity and returning to MainActivity, and after a manual GC, LeakActivity is still in memory, indicating that the table has a memory leak.

LeakActivity$1 and LeakActivity$2 refer to the first and second anonymous inner classes of LeakActivity, which are automatically named by the system at compile time as the external class name $1.class.

The four columns on the right represent each

  • Allocations: Number of instances in the Java heap.
  • Native Size: memory Size allocated by the Native layer.
  • Shallow Size: The actual Size allocated in the Java heap.
  • Retained Size: The total memory Retained (but not the actual Size) by all instances of this class.

If the Shallow Size and Retained the Size is very small and equal, can be considered have been recycled (empty Activity, is about 270). But LeakActivity is clearly not, indicating that LeakActivity has a memory leak.

Select LeakActivity on the left, and click on the right Instance to check it one by one. Now there is only one place, click on the first one, and the default Reference Depth is sorted by Depth. Only rows whose Reference Depth is less than Instance Depth are checked. Right-click -> Jump to Source to automatically locate the code.

As shown above, the expansion reveals that Handler caused the leak.

2.2. Common memory leaks

2.2.1. Static variable memory leak

The reference to the stact variable is retained by the virtual machine unless the static variable is set to NULL.

public class ToastUtil {
    private static Toast toast;
    public static void show(Context context, String message) {
        if (toast == null) {
            // The static toast variable will always hold a reference to the toast object. The toast object will not be recycled
            // If the context is an Activity context, the corresponding Activity is also leaked
            toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
        } else{ toast.setText(message); } toast.show(); }}Copy the code

Solution ① : Will makeText () the parameters in the context to context, getApplicationContext (), long life cycle of the static Toast held is also a long life cycle of Applaiction context, There would be no memory leaks.

private static Activity sActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
    // The static variable sActivity holds the Activity instance object for the life of the APP, leaking memory
    sActivity = this;
}
Copy the code

Solution 2: Disconnect static variables from their references at the end of the Acelasticity life cycle. In the onDestory() callback method, sActivity = null is executed.

2.2.2. Non-static inner class, anonymous inner class memory leak

A non-static inner class object holds a reference to an outer class object whose life cycle ends, but which may still be referenced by the inner class.

  • The compiler automatically adds a member variable to the inner class that points to a reference to an external class object.
  • The compiler automatically adds a parameter to the constructor of the inner class, which is used to assign values to the above member variables.
  • When the constructor of an inner class is called, a reference to the outer class is passed in by default.

Using the Java online decompiler tool, and using the Fernflower decompiler, you can view the decompilation:

public class Test {

    // ① Anonymous inner class implements interface and overwrites interface methods
    Runnable runnable = new Runnable() {
        @Override
        public void run(a) {}};// the anonymous inner class overrides the class method
    Object object = new Object() {
    };

    // ③ Static constant + anonymous inner class implementation interface override interface method
    static final Runnable runnable2 = new Runnable() {
        @Override
        public void run(a) {}};// ④ Non-static inner class
    class InnerClass {
        void test(a) { runnable.run(); }}// ⑤ Static inner class
    static class InnerClass2 {}}// ⑥ External class
class OuterClass {}Copy the code

Test$1.class = new Runnable() {}

import com.company.Test;
class TestThe $1implements Runnable {
    // Holds a reference to the external class
    final Test this$0;
    // A reference to an external class is passed in through the constructor
    Test$1(Test this$0) {
        this.this$0 = this$0;
    }

    public void run(a) {}}Copy the code

② Test$2.class = new Object() {}

import com.company.Test;
class Test$2 {
    // Holds a reference to the external class
    final Test this$0;
    // A reference to an external class is passed in through the constructor
    Test$1(Test this$0) {
        this.this$0 = this$0; }}Copy the code

Test$3.class static final Runnable runnable2 = new Runnable() {}

class Test$3 implements Runnable {
    public void run(a) {}}Copy the code

Test$InnerClass. Class = Test$InnerClass {}

import com.company.Test;
class Test$InnerClass {
	// Holds a reference to the external class
    final Test this$0;
	// A reference to an external class is passed in through the constructor
    Test$InnerClass(Test this$0) {
        this.this$0 = this$0;
    }
	// Access the members of the external class directly by referencing the external class
    void test(a) {
        this.this$0.runnable.run(); }}Copy the code

Test$InnerClass2. Class = static class InnerClass2 {}

class Test$InnerClass2 {
    // Does not hold references to external classes
    Test$InnerClass2() {
    }
}
Copy the code

⑥ OuterClass. Class is the OuterClass {}.

class OuterClass {
	// Does not hold references to external classes
    OuterClass() {
    }
}
Copy the code

Non-static inner class

class Outer {
    // A non-static inner class holds a reference to an external class by default. Members and methods of the external class can be called directly in the method body
    class Inner1 { / *... * / }
    // The static inner class does not hold references to the external class. The method body cannot call the members and methods of the external class directly
    static class Inner2 { / *... * /}}Copy the code

【 Anonymous Inner class 】

  • Direct new Interface, direct new Abstract class, direct new class and override (or just curly braces, but not overwrite) the member methods in them, rather than manually creating a new class to inherit or implement them and override methods. Because the new class is not explicitly named, the compiler automatically generates an anonymous inner class for each of them.
  • The compiler automatically creates anonymous inner classes as non-static inner classes and names themThe external class name is $1.class, ‘ ‘external class name $2.class’ and so on.
// Simply new an interface and override the member methods
new Runnable() {
    @Override
    public void run(a) { / *... * /}};// Simply new an abstract class and override the member methods
new AbstractSet<Object>(){
    @NonNull
    @Override
    public Iterator<Object> iterator(a) { / *... * / }
    @Override
    public int size(a) { / *... * /}};// Simply new a class and override the member methods
new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) { / *... * /}};// New a class directly, just write curly braces, but do not override any member methods. The same goes for abstract classes and interfaces
new Handler() {};
Copy the code

Common Handler memory leaks

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) { / *... * / }
// New Handler() overwrites the method, and the compiler automatically generates a non-static inner class for MainActivity$1.class
// Therefore, the new object will hold a reference to the MainActivity object by default, resulting in a memory leak
// The MSG may not be processed in MessageQueue after the Activity exits, so the Activity object cannot be reclaimed
    };
}
Copy the code

Solution: Change to static inner classes + weak references, because static inner classes don’t depend on the members and methods of external classes.

Similarly, direct new threads, new Runnable, new AsyncTask, and new Listiner can also cause memory leaks.

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    Static constants do not hold references to external classes, do not leak memory, but generate anonymous inner classes
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run(a) { / *... * /}};@Override
    protected void onCreate(Bundle savedInstanceState) {
        mHandler = new MyHandler(this);
    }
    @Override
    protected void onDestroy(a) {
        // It will not cause the Activity to leak, but the MSG may still be in the MessageQueue and should be removed
        mHandler.removeCallbacksAndMessages(null);
    }
    // Write your own static inner class and don't let the compiler automatically generate non-static inner classes
    private static class MyHandler extends Handler {
        // Use weak references to hold references to external classes
        private WeakReference<MainActivity> activityWeakReference;
        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) { / *... * /}}}Copy the code

2.2.3. Singleton mode memory leaks

The static nature of a singleton makes its lifetime as long as the lifetime of the App, and if a singleton holds a reference with a short lifetime, it is prone to memory leaks.

public class Singleton {
    // Static variables always hold Singleton objects
    private static Singleton singleton = null;
    // The Singleton object holds the Context object
    private Context mContext;
    private Singleton(Context mContext) {
        this.mContext = mContext;
    }
    public static Singleton getSingleton(Context context){
        // If the context is an Activity context, the Activity will never be reclaimed, resulting in a memory leak
        if (null == singleton) singleton = new Singleton(context);
        returnsingleton; }}public class Singleton {
    // Static variables always hold Singleton objects
    private volatile static Singleton singleton = null;
    // The Singleton object holds the Context object
    private Context mContext;
    private Singleton(Context mContext) {
        this.mContext = mContext;
    }
    public static Singleton getSingleton(Context context){
        if (singleton == null) {
            synchronized (Singleton.class){
                if (singleton == null) {
                    // If the context is Activity, the Activity memory leaks
                    singleton = newSingleton(context); }}}returnsingleton; }}Copy the code

Solution (1) : call incoming ApplicationContext or change to this. MContext = mContext. GetApplicationContext (); .

Solution 2: Change the reference mode of this attribute to weak reference.

public class Singleton {
    private volatile static Singleton singleton = null;
    // Use weak references to make the Singleton object hold the Context object
    private WeakReference<Context> mContextWeakRef;
    private Singleton(Context mContext) {
        this.mContextWeakRef = new WeakReference<Context>(mContext);
    }
    public static Singleton getSingleton(Context context){
        if (singleton == null) {
            synchronized (Singleton.class){
                if (singleton == null) {
                    // If the context is Activity, the Activity memory leaks
                    singleton = newSingleton(context); }}}returnsingleton; }}Copy the code

2.2.4. System service memory leaks

When using a system service, the getSystemService() method is called, and it is possible to register a listener for the system service, both of which can cause a memory leak. Similarly, BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., need to close, unregister, or free memory in time in onDestry.

// Call the getSystemService method directly, usually an Activity, so the system service will always hold a reference to it
SensorManager sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sm.getDefaultSensor(Sensor.TYPE_ALL);
// When a setup listener is passed into the Activity, the system service will always hold its reference
sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
Copy the code

Solutions:

  • usegetApplicationContext().getSystemService()Obtain system services.
  • inonDestoryinsideLogout listenerTo disconnect the system service from the Activity.

2.3. Prevent memory leaks

  • The Context is preferredApplicationContext
action Application Service Activity
Start the Activity ⭕ ️ New Task ⭕ ️ New Task ⭕ ️
The pop-up Dialog ⭕ ️
Instantiate layout ⭕ ️ ⭕ ️ ⭕ ️
Start the Service ⭕ ️ ⭕ ️ ⭕ ️
Binding Service ⭕ ️ ⭕ ️ ⭕ ️
Send broadcast ⭕ ️ ⭕ ️ ⭕ ️
Register broadcast receiver ⭕ ️ ⭕ ️ ⭕ ️
Loading res resources ⭕ ️ ⭕ ️ ⭕ ️
  • An inner class object with a lifetime longer than the Activity, and the inner class uses a member variable of the outer class

    1. Manually rewriting inner classes to static inner classes;
    2. Using weak references in static inner classes to refer to member variables of the outer class;
  • Objects that are no longer used are explicitly assigned null, such as Bitmap, after a call to recycle().

2.4. LeakCanary tools

Join in the dependencies debugImplementation ‘com. Squareup. Leakcanary: leakcanary – android: 2.6’, no need to add any calling code.

When a memory leak occurs, LogCat prints the information:

==================================== HEAP ANALYSIS RESULT ==================================== 1 APPLICATION LEAKS References underlined with "~~~" are likely causes. Learn more at https://squ.re/leaks. 333202 bytes retained by leaking  objects Displaying only 1 leak trace out of 2 with the same signature Signature: D01f393e64386a95c368a4c4209c91c136f7720 ┬ ─ ─ ─ │ GC Root: Local trace In native Code │ ├─ Android. OS.HandlerThread Instance bridge: Atmospheric Leaking NO (PathClassLoader↓ is not leaking) │ Thread name: 'LeakCanary - Heap - Dump │ left Thread. ContextClassLoader ├ ─ dalvik. System. PathClassLoader instance │ Leaking: NO (ToastUtil left is not leaking and this is A never leaking) │ left this. RuntimeInternalObjects ├ ─ Java.lang.object [] array │ Leaking: NO (ToastUtil left is not leaking) │ left Object [], [188] ├ ─ com. Example. Androidtest. ToastUtil class │ leaking: NO (a class is never leaking) │ ↓ static ToastUtil. Toast │ ~~~~~ ├─ Android. YES (This toast is done showing (Toast.mTN.mWM ! MView == NULL)) │ Retaining 166.6 kB in 1384 objects │ mContext instance of Com. Example. Androidtest. LeakActivity with mDestroyed = true │ left Toast. MContext ╰ - > com. Example. Androidtest. LeakActivity instance Leaking: YES (ObjectWatcher was watching this because com.example.androidtest.LeakActivity received Activity#onDestroy() callback And Activity#mDestroyed is true) Retaining 69.9kb in 1256 objects key = 1f27bf38-b690-4983-b3f1-64bd991e7f9f watchDurationMillis = 7553 retainedDurationMillis = 2549 mApplication instance of android.app.Application mBase instance  of android.app.ContextImpl ==================================== 0 LIBRARY LEAKS A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over. See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks ==================================== 0 UNREACHABLE OBJECTS An unreachable object is still in memory but LeakCanary could  not find a strong reference path from GC roots. ==================================== METADATA Please include this in bug reports and Stack Overflow questions. Build.VERSION.SDK_INT: 29 Build.MANUFACTURER: samsung LeakCanary version: 2.6 App process name: com.example. Androidtest Stats: LruCache[maxSize=3000,hits=2715,misses=45083,hitRate=5%] RandomAccess[bytes=2304319,reads=45083,travel=16121653814,range=16075212,size=20801120] Heap dump reason: 5 retained objects, app is visible Analysis duration: 3857 ms Heap dump file path: /data/user/0/com.example.androidtest/cache/leakcanary/2021-03-02_13-54-08_891.hprof Heap dump timestamp: 1614664454960 Heap dump duration: 1642 ms ====================================Copy the code

The principle of

  • RefWatcher.watch()Created aKeyedWeakReferenceUsed to observe objects.
  • Then, in the background thread, it checks to see if the reference has been cleared and if GC has not been triggered.
  • If the reference is still not cleared, it will store the stack information in the.hprof file on the file system.
  • HeapAnalyzerServiceIs started in a separate process, andHeapAnalyzerUsing theHAHAThe open source library parses the heap dump, a stack snapshot file, at a given time.
  • fromheap dump,HeapAnalyzerAccording to a unique quotekeyTo find theKeyedWeakReferenceAnd locate the leaked reference.
  • HeapAnalyzerTo determine if there was leakage, the shortest strong reference path to GC Roots was calculated, and then the chain reference leading to leakage was established.
  • The result is passed back to the app processDisplayLeakService“And a leak notification is displayed.