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:
-
Non-static inner classes (member inner classes)
-
Static inner classes (nested inner classes)
-
Local inner classes (classes defined in a method or scope, like local variables, so no access-control characters, static, etc.)
-
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.
-
Interface anonymous inner class
-
Abstract classes anonymous inner classes
-
Class anonymous inner class
Anonymous inner class summary:
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.
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
-
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.
-
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