In android development, memory leaks are a relatively common problem. Children who have some experience in android programming should have encountered it, but why do memory leaks occur? What are the effects of memory leaks?
In android program development, when an object is no longer needed and should be recycled, but another object in use holds a reference to it, causing it to not be recycled, this leads to Objects that should be recycled cannot be recycled and stay in the heap memory, and a memory leak occurs.
What is the impact of memory leaks? It is one of the main causes of application OOM. Since the memory allocated by the Android system for each application is limited, when there are many memory leaks in an application, it will inevitably cause the memory required by the application to exceed the memory limit allocated by the system, which causes a memory overflow and causes the application to Crash.
After understanding the causes and effects of memory leaks, what we need to do is to master common memory leaks and try to avoid them in future Android program development.
Memory allocation in Java
Static storage area: It is allocated during compilation and exists throughout the running 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 Area: usually stores objects produced by new. Recycled by Java garbage collector.
Introduction to four reference types
Strong Reference: JVM would rather throw OOM than let GC recycle objects with strong references;
Soft reference (SoftReference): An object that will only be returned when there is insufficient memory space;
Weak reference (WeakReference): During GC, once an object with only a weak reference is found, no matter whether the current memory space is sufficient or not No, its memory will be recycled;
Virtual reference (PhantomReference): It can be recycled by GC at any time. When the garbage collector is ready to recycle an object, if it finds that it still has a virtual reference, it will be recycled Before entering the object's memory, add this virtual reference to the reference queue associated with it. The program can learn whether the object is about to be recycled by determining whether there is a virtual reference to the object in the reference queue. Can be used as a flag for GC to recycle Object.
The memory leak we often say means that the new Object cannot be recycled by GC, that is, it is a strong reference:
The main manifestations when memory leaks occur Due to memory jitter, the available memory slowly decreases:
#The tool MAT
MAT (Memory Analyzer Tools) in Andriod to analyze memory leaks is an Eclipse plug-in , it is a fast, feature-rich JAVA heap analysis tool, which can help us find memory leaks and reduce memory consumption.
How to monitor memory leaks in QQ and Qzone
The memory leaks in QQ and Qzone adopt the SNGAPM solution. SNGAPM is a unified solution for performance monitoring and analysis. It collects performance information from the terminal and reports it Go to a backend, which aggregates monitoring information and displays it as a chart, analyzes the analysis information and submits orders, and notifies developers;
SNGAPM consists of two parts: App (MagnifierApp) and web server (MagnifierServer);
MagnifierApp is an intermediate platform that connects the detection component (LeakInspector) and automated cloud analysis (MagnifierCloud) in automatic memory leak detection. It automatically uploads the MagnifierServer from the memory dump of LeakInspector;
MagnifierServer background Analysis tasks will be submitted to MagnifierCloud regularly;
After the MagnifierCloud analysis is completed, the data will be updated on the magnifier web and developers will be notified in the form of a bug ticket.
Common memory leak cases
case 1. Memory leak caused by singleton
The static characteristics of singleton cause its life cycle to be as long as the application.
Solution:
Change the reference method of this attribute to a weak reference;
If you pass in Context, use ApplicationContext;
example: leaked code Snippet
private static ScrollHelper mInstance; private ScrollHelper() { } public static ScrollHelper getInstance() { if (mInstance == null) { synchronized (ScrollHelper.class) { if (mInstance == null) { mInstance = new ScrollHelper(); } } } return mInstance; } /** * 被点击的view */ private View mScrolledView = null; public void setScrolledView(View scrolledView) { mScrolledView = scrolledView; }
Solution: Use WeakReference
private static ScrollHelper mInstance; private ScrollHelper() { } public static ScrollHelper getInstance() { if (mInstance == null) { synchronized (ScrollHelper.class) { if (mInstance == null) { mInstance = new ScrollHelper(); } } } return mInstance; } /** * 被点击的view */ private WeakReference<View> mScrolledViewWeakRef = null; public void setScrolledView(View scrolledView) { mScrolledViewWeakRef = new WeakReference<View>(scrolledView); }
case 2. InnerClass anonymous inner class
In Java, both non-static inner classes and anonymous classes potentially reference the object they belong to Outer classes, however, static inner classes do not. If this non-static inner class instance performs some time-consuming operations, the surrounding objects will not be recycled, resulting in memory leaks.
Solution:
Change the inner class into a static inner class;
If there is a strong reference to an attribute in Activity, change the reference method of the attribute to a weak reference ;
If the business permits, end these time-consuming tasks when the Activity executes onDestory;
example:
public class LeakAct extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } //这儿发生泄漏 public void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
Solution:
public class LeakAct extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } //加上static,变成静态匿名内部类 public static void test() { new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
case 3. Incorrect use of Activity Context
There are usually two types of Context objects that can be used in Android applications: Activity and Application. A common practice when a class or method requires a Context object is to use the first one as the Context parameter. This means that the View object maintains a reference to the entire Activity, and therefore all references to the Activity.
Assume a scenario, when the application has a relatively large Bitmap type picture, it takes a lot of time to reload the picture each time it is rotated. In order to improve the speed of screen rotation and Activity creation, the simplest method is to use Static decoration on this Bitmap object. When a Drawable is bound to a View, the View object actually becomes a Callback member variable of the Drawable. The life cycle of static variables is longer than that of Activity. As a result, when the screen is rotated, the Activity cannot be recycled, causing a memory leak.
Solution:
Use ApplicationContext instead of ActivityContext, because ApplicationContext will exist with the existence of the application and does not depend on the life cycle of the activity;
Regarding Context The reference should not exceed its own life cycle, and use the "static" keyword for Context carefully. If there are threads in the Context, they must be stopped in onDestroy() in time.
example:
private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
Solution:
private static Drawable sBackground; @Override protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); label.setText("Leaks are bad"); if (sBackground == null) { sBackground = getApplicationContext().getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
case 4. Memory leak caused by Handler
When there are delayed tasks or tasks waiting to be executed in the Handler The task queue is too long. Since the message holds a reference to the Handler, and the Handler holds a potential reference to its external class, this reference relationship will remain until the message is processed, resulting in the Activity being unable to be recycled by the garbage collector. , which resulted in a memory leak.
Solution:
You can put the Handler class in a separate class file, or use a static inner class to avoid leakage;
If you want to call it inside the Handler If the activity is located, you can use a weak reference inside the handler to point to the activity. Use Static + WeakReference to disconnect the reference relationship between the Handler and the Activity.
Solution:
@Override protected void doOnDestroy() { super.doOnDestroy(); if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } mHandler = null; mRenderCallback = null; }
case 5. Leakage of registered listener
系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。
解决方案:
使用ApplicationContext代替ActivityContext;
在Activity执行onDestory时,调用反注册;
mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);
Solution:
mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
下面是容易造成内存泄漏的系统服务:
InputMethodManager imm = (InputMethodManager) context.getApplicationContext().get SystemService(Context.INPUT_METHOD_SERVICE);
Solution:
protected void onDetachedFromWindow() { if (this.mActionShell != null) { this.mActionShell.setOnClickListener((OnAreaClickListener)null); } if (this.mButtonShell != null) { this.mButtonShell.setOnClickListener((OnAreaClickListener)null); } if (this.mCountShell != this.mCountShell) { this.mCountShell.setOnClickListener((OnAreaClickListener)null); } super.onDetachedFromWindow(); }
case 6. Cursor,Stream没有close,View没有recyle
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。
Solution:
调用onRecycled()
@Override public void onRecycled() { reset(); mSinglePicArea.onRecycled(); }
在View中调用reset()
public void reset() { if (mHasRecyled) { return; } ... SubAreaShell.recycle(mActionBtnShell); mActionBtnShell = null; ... mIsDoingAvatartRedPocketAnim = false; if (mAvatarArea != null) { mAvatarArea.reset(); } if (mNickNameArea != null) { mNickNameArea.reset(); } }
case 7. 集合中对象没清理造成的内存泄漏
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
解决方案:
在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。
Solution
private List<EmotionPanelInfo> data; public void onDestory() { if (data != null) { data.clear(); data = null; } }
case 8. WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。
解决方案:
为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
case 9. 构造Adapter时,没有使用缓存的ConvertView
初始时ListView会从Adapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象 缓存起来。
当向上滚动ListView时,原先位于最上面的List Item的View对象会被回收,然后被用来构造新出现的最下面的List Item。
这个构造过程就是由getView()方法完成的,getView()的第二个形参View ConvertView就是被缓存起来的List Item的View对象(初始化时缓存中没有View对象则ConvertView是null)。