写在前面

内存泄漏实际上很多时候,对于开发者来说不容易引起重视。因为相对于crash来说,android中一两个地方发生内存泄漏的时候,对于整体没有特别严重的影响。但是我想说的是,当内存泄漏多的时候,很容易造成他OOM的,因为android给每个app的分配的内存是有限的,而且当发生内存泄漏的时候,对于app的体验也会不好,容易造成卡顿等不好的体验。

Java内存结构


上面展示的是Java虚拟机运行时数据区的模型图

这里简单介绍下

  • 方法区:存储已加载的类信息,常量池,静态变量(静态变量与app的生命周期同步)
  • 虚拟机栈:存储基本类型和对象引用
  • 堆:存储新建的对象或数组对象
  • 程序计数器:线程执行字节码文件位置的指示器,用于线程切换后,再次切换回来能够准确执行上次执行到的字节码文件位置。
  • 本地方法栈:用于记录Native方法。

我们主要关注这几个内存区域,new出来的对象,放在Heap堆这个区域,这块内存区由GC(Garbage Collector)负责内存的回收。对于Java程序员来说,很多时候我们不需要关心内存的分配与回收问题,但是这并不表示Java没有内存泄漏,Java的内存泄漏显得更为隐蔽,于是这不仅需要我们在开发时避免写出容易造成内存泄漏的问题代码,还需要我们在出现内存泄漏时掌握相应的技巧去定位和发现问题。

Java 对象在内存中的三种状态

  1. 可达状态
    当一个对象被创建后,有一个以上的引用变量引用它。在有向图中可以从起始顶点导航到该对象,那它就处于可达状态,程序可通过引用变量来调用该对象的属性和方法。
  2. 可恢复状态
    如果程序中某个对象不再有任何引用变量引用它,它将先进入可恢复状态,此时从有向图的起始顶点不能导航到该对象,在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存。在回收该对象之前,系统会调用可恢复状态的对象的finalize方法进行资源清理,如果系统在调用finalize方法重新让一个以上的引用变量引用该对象,则这个对象会再次变为可达状态;否则,该对象进入不可达状态。
  3. 不可达状态
    当对象的所有关联被切断,且系统调用所有对象的finalize方法依然没有使得该对象变为可达状态,则这个对象将永久性地失去引用,最后变为不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源。

    左边的object1,object2,object3和object4是仍然存活的对象,或者说可达状态的对象,而右边的object5,object6和object7则是判定可回收的状态,或者说不可达状态的对象

这里说到的GC Roots,一般作为GC Roots的对象有下面几个(书本上的)可达性算法的根节点(简单来说,这些对象一般不会被GC 回收):
a 虚拟机栈(栈桢中的本地变量表)中的引用的对象

b.方法区中的类静态属性引用的对象

c.方法区中的常量引用的对象

d.本地方法栈中JNI的引用的对象

为什么会有内存泄漏

上面所说的不可达状态的判断,在Java中是由 可达性分析算法 来实现的。其基本思路就是通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可达的。
在android中尤为明显,因为android中很多对象都是有生命周期的。当它们的任务完成之后,它们将被垃圾回收。如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏。

简单来说,没有被GC ROOTS间接或直接引用的对象的内存会被回收。

那么为什么会有内存泄漏呢
来看一段Java代码:

List list = new ArrayList();
for (int i = 1; i < 100; i++) {Object object = new Object();list.add(object);object = null;
}

针对上面的代码我大致画了下简图,我们将object置为null,object变为无用对象,但是GC这时并不能回收object的内存,因为list仍然在引用object对象,故这就会导致内存泄漏。

我们循环申请Object对象,并将所申请的对象放入一个 ArrayList 中,如果我们仅仅释放引用本身,那么 ArrayList 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到ArrayList后,还必须从 ArrayList 中删除。或者我们将ArrayList 对象设置为 null。

其实简单来说内存泄漏就是当对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用。

android中的内存泄漏

来到我们的主题,android中的内存泄漏大部分是指Activity的内存泄漏,因为Activity对象会间接或者直接引用View,Bitmap等,所以一旦无法释放,会占用大量内存。

android内存泄漏的具体场景

错误使用Context

public class Singleton {private static Singleton instance;private Context mContext;private Singleton(Context context) {this.mContext = context;}public static Singleton getInstance(Context context) {if(instance == null) {instance = new Singleton(context);}return instance;}
}

项目中封装许多工具类,这些工具类有的会被设计成单例模式。

很正常的一个单例模式,可就由于传入的是一个 Context,而这个 Context 的生命周期的长短就尤为重要了。如果我们传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出的时候,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会回收,最后造成内存泄漏。

这里我们在设计单例的时候尽量把这部分拦截下来,尽量使用ApplicationContext,而不用生命周期短的Activity,容易造成Activity的内存泄漏。

Handler使用造成内存泄漏

我们在界面上经常会有自动滚动的Banner以及用TextSwitcher实现的自动轮播的公告消息的功能,这样的自动滚动通常通常都是通过Handler来不断的发消息来实现自动滚动,如下面的代码。由于这样的是一个耗时操作,当前Activity被finish掉的时候,Handler仍然持有外部类Activity的引用,导致Activity不能被GC回收,所以导致内存泄露。

import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;public class MainActivity extends AppCompatActivity {private static final int TYPE_CHANGE_AD = 1;private Handler mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message message) {if(message.what == TYPE_CHANGE_AD) {//do something}return false;}}) ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {mHandler.sendEmptyMessage(TYPE_CHANGE_AD);}},5000);}
}

针对Handler造成的内存泄漏,一般有两种方法来解决:

  • 第一种是在Activity onDestroy的时候,将一些已经在排队的msg remove掉,通过removeCallbacksAndMessages来把在当前Handler持有的消息队列的msg移除掉。
  • 第二种是使用WeakReference来处理。
public class MainActivity extends Activity {private static class MyHandler extends Handler {private final WeakReference<MainActivity> mActivity;public MyHandler(MainActivity activity) {mActivity = new WeakReference<MainActivity>(activity);}@Overridepublic void handleMessage(Message msg) {SampleActivity activity = mActivity.get();if (activity != null) {// ...}}}private final MyHandler mHandler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {mHandler.sendEmptyMessage(TYPE_CHANGE_AD);}},5000);finish();}
}

强引用,软引用,弱引用,虚引用

与Handler有点类似的,我们在Activity中使用Thread(或者TimeTask)的时候去做一些耗时操作的时候,也很容易造成内存泄漏。通常我们在Activity的里面通常都是采用内部类的时候来用的,这种跟Handler机制一样,因为内部类默认都会持有外部类的引用,当Activity finish的时候,Thread里面的操作可能还没结束,这时Activity被Thread所占用,导致无法回收。针对这种情况,网上建议是采用静态内部类,因为静态内部类是不会持有外部类的引用的。

针对这种情况,最好的方式我觉得还是用rxjava来做,能用rxjava来做的尽量用rxjava做。

InputMethodManager造成的内存泄漏

使用LeakCanary检测到的内存泄漏,LeakCanary是Square公司是开发的用于检测内存泄漏的开源库,我们知道Java最常见的内存分析工具是MAT,MAT的使用比较简单,但是用MAT分析内存相对比较麻烦。而LeakCanary这个开源库则不一样,在简单的代码引入LeakCanary的时候,它就能够替我们检测程序中的内存泄漏,当出现内存泄漏的时候还能以一个非常友好的节目来显示内存泄漏的引用链。如下图所示:

上面展示的是InputMethodManager造成的内存泄漏,InputMethodManager造成的内存泄漏看起来比较奇怪,毕竟是Google设计的API,查了一些资料,InputMethodManager.mServedView持有一个最后聚焦View的引用,直到另外的一个View聚焦后才会释放当前的View。当发生GC时mServedView持有的View的引用不会被回收,导致了内存泄漏。

针对InputMethodManager造成的内存泄漏,LeakCanary这个库在AndroidExcludedRefs.java页列举了很多内存泄漏的场景,同时针对InputMethodManager也有提供了相应的解决方法。

在反编译今日头条的代码发现有下面这段代码,发现这段代码是通过反射来解决InputMethodManager造成的内存泄露,反射这种方式相对比较暴力点,但是针对InputMethodManager造成的内存泄漏目前没发现比较好的方式。

 public static void releaseInputMethodManagerFocus(Activity paramActivity){if (paramActivity == null);while (true){return;try{InputMethodManager localInputMethodManager = (InputMethodManager)paramActivity.getSystemService("input_method");if (localInputMethodManager != null){Method localMethod = InputMethodManager.class.getMethod("windowDismissed", new Class[] { IBinder.class });if (localMethod != null)localMethod.invoke(localInputMethodManager, new Object[] { paramActivity.getWindow().getDecorView().getWindowToken() });paramActivity = InputMethodManager.class.getDeclaredField("mLastSrvView");if (paramActivity != null){paramActivity.setAccessible(true);paramActivity.set(localInputMethodManager, null);return;}}}catch (Throwable paramActivity){paramActivity.printStackTrace();}}}

使用高德地图来地位出现的内存泄露,由于定位会在多个页面使用,所以讲高德地图定位抽取成了一个工具类,

下面这个是高德地图的内存泄漏

这里出现内存泄漏场景一般是都是我们写的代码不正确导致的。
拿高德地图举例:

第一是我们通过AMapLocationClient去开启地位的时候,不要忘了在Activity Destroy的时候调用stopLocation和onDestroy方法。
第二是我们的MapView需要与Activity的生命相绑定,不然也容易造成内存泄漏。

与高德地图这样的类似就是,SensorManager,EventBus,BroadCast的注册,在调用register方法后不要忘记调用unregister方法。

WedView造成的内存泄漏

WebView跟MapView有点类似,对于WebView首先要注意的是,Activity onDestroy的时候注意将WebView移除掉:

 @Overridepublic void onDestroy() {super.onDestroy();if (mWebview != null) {if (mWebview.getParent() != null) {((ViewGroup) (mWebview.getParent())).removeView(mWebview);}mWebview.destroy();mWebview = null;}}

但是对于WebView需要加载大量数据的时候,例如需要加载很多图片的时候,这个时候对于这种场景可以考虑将WebView放进一个垃圾进程,在Activity onDestroy的时候,需要调用Process.killProcess(Process.myPid())将当前进程kill掉。

@Overridepublic void onDestroy() {super.onDestroy();Process.killProcess(Process.myPid())}
  • 有效增大App的运存,减少由webview引起的内存泄露对主进程内存的占用。
  • 避免WebView的Crash影响App主进程的运行。
  • 拥有对WebView独立进程操控权。

采用这种方式可能需要设计到进程间通信相关,aidl,messager。

关于Activity内存泄漏的场景很多,但是无论是何种案例,都离不开一些表格中集中大的分类。

引用的方式/GC Root Class-(静态变量) 活着的线程 生命周期跟随app的特殊存在
mContext间接引用 静态View,InputMethodManager SensorManager、WifiManager(其他Service进程都可以) ViewRootImpl
(this$0间接引用) 内类引用 匿名类/Timer/TimerTask/Handler
数据库,流等资源未释放

这里首先简单说明下,

  • 第一种:Activity的Context被静态变量所持有导致内存泄漏
class Person {static Object obj = new Object();
}

根据jvm的分代的回收机制,Person类的信息将会被存入方法区永久(Permanent)代。也就是说,Person类、obj引用变量都将存在Permanent里,这会导致obj对象一直有效,从而使得obj对象不能被回收。
同样的道理,Activity里面的静态变量同样会造成Activity不能被回收从而导致内存泄漏。

  • 第二种:内部类(this$0)造成Activity的泄漏
    内部类是很容易造成内存泄漏的,因为内部类都能够访问外部类的成员变量,默认内部类都会持有外部类的引用,即this$0这样的字串,当内部类和外部类的生命周期不一致的时候,在android中最典型就是Handler了。

Message对象有个target字段,该字段是Handler类型,引用了当前Handler对象。一句话就是:你通过Handler发往消息队列的Message对象持有了Handler对象的引用。假如Message对象一直在消息队列中未被处理释放掉,你的Handler对象就不会被释放,进而你的Activity也不会被释放。这种现象很常见,当消息队列中含有大量的Message等待处理,你发的Message需要等几秒才能被处理,而此时你关闭Activity,就会引起内存泄露。如果你经常send一些delay的消息,即使消息队列不繁忙,在delay到达之前关闭Activity也会造成内存泄露。

  • 第三种 资源没释放(数据库连接,Bitmap,IO流)
3634 3644 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
3634 3644 E JavaBinder: android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=866 (# cursors opened by pid 1565=866)
3634 3644 E JavaBinder: at android.database.CursorWindow.(CursorWindow.java:104)
3634 3644 E JavaBinder: at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:147)
3634 3644 E JavaBinder: at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:141)
3634 3644 E JavaBinder: at android.database.CursorToBulkCursorAdaptor.getBulkCursorDescriptor(CursorToBulkCursorAdaptor.java:143)
3634 3644 E JavaBinder: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:118)
3634 3644 E JavaBinder: at android.os.Binder.execTransact(Binder.java:367)
3634 3644 E JavaBinder: at dalvik.system.NativeStart.run(Native Method)

数据库的Cursor在使用完要记得close,Bitmap如果是采用createBitmap这种方法创建的在释放的时候要记得调用recycle方法,IO流在使用之后要记得close,这种比较小的地方虽然不是必现问题,但是会占用资源,这种未释放的资源多了场景多了很容易造成内存泄漏。

写在最后

  • 内类是非常危险的编码
  • 在使用Handler,Thead,TimeTask的时候需要需要注意
  • 对于资源一定要记得释放
  • 对于一些系统API容易造成内存泄漏的地方可以重点关注下。

谈谈android中的内存泄漏相关推荐

  1. Android中的内存泄漏

    ** Android中的内存泄漏 ** Android中的内存泄漏: 概念:程序在申请内存后,当该内存不需再使用但却无法被释放 & 归还给程序的现象,对应用程序的影响,容易使得应用程序发生内存 ...

  2. Android中的内存泄漏和内存溢出

    一.内存泄漏 1.内存泄漏的现象和本质 内存泄漏(Memory Leak)是指某些对象已经不再使用了,但却无法被垃圾回收器回收内存,还一直占用着内存空间的现象,这就导致这一块内存泄露了. 而垃圾回收器 ...

  3. Android 中的内存泄漏和内存溢出

    博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家,

  4. 深入理解Java中的内存泄漏

    理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...

  5. Android开源框架——内存泄漏检测工具 LeakCanary

    开源地址:https://github.com/square/leakcanary FAQ : https://github.com/square/leakcanary/wiki/FAQ 配置 bui ...

  6. android中内存泄露,Android中的内存泄露

    编辑推荐: 本文来自于csdn,本文主要从java的内存模型讲起,最终举出几个内存泄露的例子和解决方案. java运行时内存模型 具体信息:http://gityuan.com/2016/01/09/ ...

  7. Java应用程序中的内存泄漏和内存管理

    Java平台最突出的功能之一是其自动内存管理. 许多人错误地将此功能转换为Java中没有内存泄漏 . 但是,事实并非如此,我给人的印象是,现代Java框架和基于Java的平台,尤其是Android平台 ...

  8. 探究Bitmap在Android中的内存占用

    一.Bitmap的内存占用检测 Bitmap 一直以来都是 Android App 的内存消耗大户,很多 Java 甚至 native 内存问题的背后都是不当持有了大量大小很大的 Bitmap,我们可 ...

  9. Java中关于内存泄漏出现的原因以及如何避免内存泄漏

    转账自:http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静 ...

最新文章

  1. python2 json大数据_大数据技术之python 操作json
  2. Swarm 如何存储数据?- 每天5分钟玩转 Docker 容器技术(103)
  3. 逐行计算、逐行递延、逐行更新
  4. 日志文件列表 读书笔记《Linux 系统管理技术手册(第二版)》
  5. CodeForces - 743B Chloe and the sequence
  6. nodejs+express +jade模板引擎 新建项目
  7. c语言数据结构学习心得——线性表
  8. faceswap深度学习AI实现视频换脸详解
  9. Redis数据结构——字典-hashtable
  10. 第二章16位和32位微处理器(2)——一些操作时序与中断
  11. substring splice
  12. 拉格朗日插值法_Lagrange、Newton、分段插值法及Python实现
  13. 黑苹果使用Hackintool注入声卡驱动
  14. tsp 近似算法 matlab,TSP问题—近似算法
  15. android 键盘 横屏 边框,Android横屏时软键盘全屏问题
  16. cocos2dx 游戏中内存优化
  17. 假如举行一场世界功夫大赛,这33位动作片明星谁可跻身前十?
  18. 公路路基路面回弹弯沉检测技术(转载)
  19. 别费劲找站长工具共享VIP了 这个工具也不错
  20. 在2021年为七夕Python程序与Docker牵线配对

热门文章

  1. 王建兴:给技术焦虑者及狂热者的建议
  2. 《python初级爬虫》(二)
  3. 九度OJ 题目1203:IP地址
  4. curl报错 curl: option --form: is badly used here
  5. ENSP模拟器防火墙增加内存
  6. 真实评测:天玑1000+相当于骁龙什么处理器-联发科天玑1000+处理器相当于骁龙多少
  7. mysql 离线报表,FineReport报表工具6.5离线填报示例
  8. 前端将后端返回的富文本格式字符串转换成word下载
  9. potplayer恢复默认配置
  10. HBase 统计表中数据量