Java memory reclamation

Instead, it is the reachability analysis algorithm that determines whether an object is recyclable.

In the mainstream commercial programming languages (Java and C#), reachability analysis algorithms are used to determine whether objects are alive or not. The basic idea of this algorithm is to search down from a series of objects named “GC Roots” as the starting point, and the search path is called the Reference Chain. When an object is not connected to GC Roots by any Reference Chain, it is proved that the object is unavailable. Object5, Object6, object7 are not reachable to GC Roots, so they will be judged as recyclable objects.

In the Java language, GC Roots objects include the following:

A. Object referenced in the virtual machine stack (local variable table in the frame) b. Object referenced in the class static attribute in the method area C. D. Objects referenced by JNI in the local method stackCopy the code

From the deep understanding of the Java virtual machine “: http://www.roncoo.com/course/view/0ec92a81e8764b838e86c4febe7a5b17

Use leakCanary to detect leaks

LeakCanary’s memory leak prompt usually contains three parts: The first part (the sInstance variable of the LeakSingle class) references the second part (the mContext variable of the LeakSingle class), causing the third part (instance of the MainActivity class) to leak.

Leakcanary Use caution

Even if an empty Activity detects leaks like this, note that refwatcher.watct () can be handled by placing it inside onDestroy, or ignore the prompt. Since the article has been written a lot, the following will not be modified, just ignore this error.

* com.less.demo.TestActivity has leaked: * GC ROOT static android.app.ActivityThread.sCurrentActivityThread * references android.app.ActivityThread.mActivities *  references android.util.ArrayMap.mArray * references array java.lang.Object[].[1] * references android.app.ActivityThread$ActivityClientRecord.activity * leaks com.less.demo.TestActivity instanceCopy the code
protected void onDestroy() {    super.onDestroy();
    RefWatcher refWatcher = App.getRefWatcher(this);
    refWatcher.watch(this);
}Copy the code

Leakcanary and code examples describe memory leaks

Case 1 (Memory leak caused by static members)

public class App extends Application { private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { App application = (App) context.getApplicationContext(); return application.refWatcher; }}Copy the code

The inner class is static (gC-root, which will always be attached to the external class instance). Static and Application lifetime will result in holding the external class reference (the inner class implicitly has a member variable $0), even if the external class is null.

OutterClass

public class OutterClass { private String name; class Inner{ public void list(){ System.out.println("outter name is " + name); }}}Copy the code

TestActivity

Public Class TestActivity extends Activity {private static OutterClass.Inner Class; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); OutterClass outterClass = new OutterClass(); innerClass = outterClass.new Inner(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(outterClass); // Monitor object outterClass = null; }Copy the code

Case 2 (Memory leak caused by singleton pattern)

DownloadManager

public class DownloadManager { private static DownloadManager instance; private Task task ; public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public Task newTask(){ this.task = new Task(); return task; }}Copy the code

Task

public class Task { private Call call; public Call newCall(){ this.call = new Call(); return call; }}Copy the code

Call

public class Call { public void execute(){ System.out.println("=========> execute call"); }}Copy the code

TestActivity

public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); RefWatcher refWatcher = App.getRefWatcher(this); Task task = DownloadManager.getInstance().newTask(); Call call = task.newCall(); call.execute(); refWatcher.watch(call); // Call = null; DownloadManager is a static singleton that references task, which references Call. The task cannot be recycled even if call is empty. To avoid memory leaks, disconnect GC_ROOT. }}Copy the code

Some logs are as follows: The current GC_ROOT is an instance of DownloadManager.

In com. Leakcanary. Demo: 1.0:1. * com In less. The demo. The Call from the leaked: * GC ROOT static com.less.demo.DownloadManager.instance * references com.less.demo.DownloadManager.task * references com.less.demo.Task.call * leaks com.less.demo.Call instanceCopy the code

Here are two more examples to illustrate the memory leakage problem caused by the above two methods.

Case 3 (Memory leak due to static variables)

public class TestActivity extends Activity { private static Context sContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sContext = this; RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); // Monitor object}Copy the code

The following logs are displayed:

In com. Leakcanary. Demo: 1.0:1. Com. Less. The demo. The TestActivity has leaked. GC ROOT static com.less.demo.TestActivity.sContext leaks com.less.demo.TestActivity instanceCopy the code

It can be analyzed from this log that: When you declare static, the sContext lifetime is as long as the Application lifetime. The Activity exits the desktop, the Application still exists ->sContext still exists, When the GC tries to reclaim the Activity, it finds that the Activity is still connected to the sContext(gC-root), causing it to fail to reclaim and leak memory.

The above code is modified as follows.

public class TestActivity extends Activity { private static View sView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sView = new View(this); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }}Copy the code

The log is as follows:

In com. Leakcanary. Demo: 1.0:1. Com. Less. The demo. The TestActivity has leaked. GC ROOT static com.less.demo.TestActivity.sView references android.view.View.mContext leaks com.less.demo.TestActivity instanceCopy the code

Case 4 (memory leak caused by singleton pattern) DownloadManager

public class DownloadManager { private static DownloadManager instance; private List mListeners = new ArrayList<>(); public interface DownloadListener { void done(); } public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public void register(DownloadListener downloadListener){ if (! mListeners.contains(downloadListener)) { mListeners.add(downloadListener); } } public void unregister(DownloadListener downloadListener){ if (mListeners.contains(downloadListener)) { mListeners.remove(downloadListener); }}}Copy the code

TestActivity

public class DownloadManager { private static DownloadManager instance; private List mListeners = new ArrayList<>(); public interface DownloadListener { void done(); } public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public void register(DownloadListener downloadListener){ if (! mListeners.contains(downloadListener)) { mListeners.add(downloadListener); } } public void unregister(DownloadListener downloadListener){ if (mListeners.contains(downloadListener)) { mListeners.remove(downloadListener); } } } TestActivity public class TestActivity extends Activity implements DownloadManager.DownloadListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); DownloadManager.getInstance().register(this); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } @Override protected void onDestroy() { super.onDestroy(); / / forget to unregister. / / DownloadManager getInstance (). The unregister (this); } @Override public void done() { System.out.println("done!" ); }}Copy the code

In com. Leakcanary. Demo: 1.0:1. * com In less. The demo. The TestActivity has leaked. * GC ROOT static com.less.demo.DownloadManager.instance * references com.less.demo.DownloadManager.mListeners * references java.util.ArrayList.array * references array java.lang.Object[].[0] * leaks com.less.demo.TestActivity instanceCopy the code

Does writing incorrectly always cause a memory leak?

The answer is: no. In the following cases, limited time can save memory leaks, so control the life cycle, other cases: for example, singleton (static variable) life cycle is longer than the life cycle of its holding sContext -> memory leaks, shorter -> escape memory leaks.

public class TestActivity extends Activity { private static Context sContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sContext = this; new Handler().postDelayed(new Runnable() { @Override public void run() { sContext = null; }}, 1000); // Test 1s and 12s respectively, the former will not leak, the latter will leak. So if you cut off GC_ROOT before GC, you can avoid memory leaks. } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }}Copy the code

This section describes memory leaks caused by handlers

Memory leaks caused by handlers are something most of us have probably done. Note the comments in the following code that a non-static inner class holds an external class reference, but holding does not necessarily mean leaking, but rather depending on who lives longer in gc. After testing (1) there is no memory leak.

Message. target = Handler; message.target = Handler; handler.post(message); This in turn pushes the message into a MessageQueue, which polls messages in a Looper thread, and sometimes the message is still old enough to be reused. If there are still messages unprocessed or being processed when the Activity exits, a memory leak will occur because Message references handler, which in turn references the Activity.

public class TestActivity extends Activity { private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); System.out.println("===== handle message ===="); }}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); / / (1) does not lead to memory leaks handler. SendEmptyMessageDelayed (x123 0, 0); // (2) will result in a memory leak, non-static inner classes (including anonymous inner classes such as this Handler anonymous inner class) will reference the outer class object this (such as Activity) // When it uses postDelayed, If the Activity finishes and the handler still references the Activity, it will leak memory because the handler will continue to be held by main Looper for some time. Still exists in reference. Handler. SendEmptyMessageDelayed (0 x123, 12000); } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }}Copy the code

com.less.demo.TestActivity has leaked:
* GC ROOT android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mH
* references com.android.internal.view.IInputConnectionWrapper$MyHandler.mQueue
* references android.os.MessageQueue.mMessages
* references android.os.Message.target , matching exclusion field android.os.Message#target
* references com.less.demo.TestActivity$1.this$0 (anonymous subclass of android.os.Handler)
* leaks com.less.demo.TestActivity instanceCopy the code

Knowing how this works, you can avoid memory leaks even if you write code that is prone to memory leaks. The above code in onDestroy () function call mHandler. RemoveCallbacksAndMessages (null); Remove all messages and all runnables from the message queue when the Activity exits.

Memory leaks caused by internal classes

The inner classes are roughly as follows:

  1. Non-static inner classes (member inner classes)

  2. Static inner classes (nested inner classes)

  3. Local inner classes (classes defined in a method or scope, like local variables, so no access-control characters, static, etc.)

  4. Anonymous inner class (no name, only use new object once i.e. discard class definition)

Why non-static inner classes hold external class references, static inner classes don’t hold external references.

The problem is very simple, just like static methods can only call static things, and non-static can call non-static and static things. Static -> for class, static-> for object. Look at the picture:

Anonymous inner classes take the use of local inner classes a step further by creating only one object of a local inner class without naming it.

Anonymous inner classes can be typed in one of the following ways.

  1. Interface anonymous inner class

  2. Abstract classes anonymous inner classes

  3. Class anonymous inner class

Anonymous inner class summary:

  1. The main thing is that the class definition is invalidated once, and an instance of the class is used. Depending on the nature of the inner class, callbacks and closures can be implemented.

  2. JavaScript and Python callbacks pass fuction, while Java passes Object.

Callbacks are often used in Java, and the essence of callbacks is passing an object. JavaScript and other languages pass a function (like Python, or C, using a pointer to a function). Since passing an object can carry other information, So Java thinks passing an object is more flexible than passing a function (of course Java can also reflect object passing functions with Method). See Java Core Technology

Non-static internal classes cause memory leaks (assuming dog’s lifetime) the following example will cause memory leaks where private static dog dog; The result is that Dog lives longer than TestActivity, which is bad, but note that if you change to private Dog Dog; Even if Dog is a non-static inner class, it does not cause memory leaks. After all, Dog is a family member of TestActivity, and it depends on the owner.

public class TestActivity extends Activity {
    private static Dog dog ;

    class Dog {
        public void say(){
            System.out.println("I am lovely dog!");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        dog = new Dog();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(this);
    }
}Copy the code

In com. Leakcanary. Demo: 1.0:1. * com In less. The demo. The TestActivity has leaked. * GC ROOT static com.less.demo.TestActivity.dog * references com.less.demo.TestActivity$Dog.this$0 * leaks com.less.demo.TestActivity instanceCopy the code

Which inner classes or callback functions hold outer class objects? A reflection case says it all

/** * Author: Limitless * Description: A case that tests the holding of external class objects of all types of inner classes https://github.com/wangli0 * / public class Main {/ * held outside class reference * / private IAppListener mAppListener = new IAppListener () {  private String name; @override public void done() {system.out.println (" anonymous inner class object as member variable "); }}; Private static IAppListener sAppListener = new IAppListener() {private String name; @override public void done() {system.out.println (" anonymous inner class object as static member "); }}; public static void main(String args[]) { Main main = new Main(); main.test1(); main.test2(); main.test3(); // test3 "=" test4 main.test4(); main.test5(); main.test6(); } class Dog { private String name; } /* Hold external class references */ public void test1(){Dog Dog = new Dog(); getAllFieldName(dog.getClass()); } static class Cat { private String name; Private void test2() {Cat Cat = new Cat(); getAllFieldName(cat.getClass()); } /* Hold external class references */ private void test3() {class Monkey{String name; } Monkey monkey = new Monkey(); getAllFieldName(monkey.getClass()); } /* Holds an external class reference */ private void test4() {// A place commonly used for event callback (may cause memory leaks) IAppListener IAppListener = new IAppListener() {private  String name; @override public void done() {system.out.println (" anonymous inner class "); }}; getAllFieldName(iAppListener.getClass()); Private void test5() {getAllFieldName(mapplisten.getClass ()); } private void test6() {getAllFieldName(sapplisten.getClass ()); } private void getAllFieldName(Class clazz) { System.out.println("className: ======> " + clazz.getSimpleName()); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); }}}Copy the code

The above results are sufficient to indicate that even callback objects within a method hold external class references, so while the scope is local, there is also the potential for memory leaks. I have emphasized many times that holding something does not necessarily mean revealing, depending on who lives longer. Callbacks are ubiquitous throughout Android development, and if holding them leaks memory, it’s almost impossible to play. Execute (new Listener(){xx}); It will not leak memory, and the local scope will be invalidated once this method is done. But if you execute(listener); In the process, a singleton object suddenly references the Listener object, which can cause a memory leak.

Here’s an example of my idea, TestActivity

public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); Task task = new Task(); Task.execute (new ICallback() {@override public void done() {system.out.println (" Download done! ); }}); } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }}Copy the code

Task

public class Task { public void execute(ICallback iCallback) { DownloadManager.getInstance().execute(iCallback); }}Copy the code

DownloadManager

public class DownloadManager { public static DownloadManager instance; private ICallback mICallback; public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public void execute(ICallback iCallback) { this.mICallback = iCallback; Icallback.done (); // If you comment out this line, memory leaks will not occur. }Copy the code

This is enough to prove that my idea is correct, and the bad luck of Callback when used also leads to memory leaks in sending.

conclusion

  1. If some singletons need to use a Context object, it is recommended to use the Application Context rather than the Activity Context. Otherwise, memory leaks may occur. The life cycle of a singleton is the same as that of an Application, so that the Application and the singleton are destroyed together.

  2. Static inner classes are preferred over non-static ones, because non-static inner classes holding external class references can cause garbage collection to fail. If your static inner class needs a reference to the host Activity to perform something, you’ll want to wrap the reference in a WeakReference to avoid accidental leaks of the Activity, and the object associated with the WeakReference will only survive until the next garbage collection occurs. When the garbage collector works, objects associated only with weak references are reclaimed, regardless of whether or not there is currently enough memory, indicating that the object itself is no longer useful.

public class TestActivity extends Activity { private MyHandler myHandler = new MyHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); } static class MyHandler extends Handler { private WeakReference mWeakReference; public MyHandler(Activity activity){ mWeakReference = new WeakReference(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show(); Log.d("xx", mWeakReference.get().getPackageName()); } } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }}Copy the code