内存泄露是如何产生的?

  当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

  内存泄露是造成OOM的主要原因之一。

内存泄露的对象是什么?

  内存分配有三种策略:静态(静态存储区/方法区)、栈、堆。

    静态存储区(方法区):内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量。

     栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

      堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。

  堆和栈的区别:

    栈:定义一些基本数据类型变量和应用变量。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

    堆:用于存放所有由new创建的对象(内容包括该对象其中的所有成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

    堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G),所以堆的空间比较灵活,比较大。栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在编译时确定,VC中可设置)。

    对于堆,频繁的new/delete会造成大量内存碎片,使程序效率降低。对于栈,它是先进后出的队列,进出一一对应,不产生碎片,运行效率稳定高。

  结论:

    局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。

    ——因为它们属于方法中的变量,生命周期随方法而结束。

    成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)

    ——因为它们属于类,类对象终究是要被new出来使用的。

内存为什么会泄露?

  Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

  堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的根本原因。

Java内存回收机制:

  从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链,组成无法回收的对象集合,而其他孤立对象(集)就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

  关于“引用”:通俗的讲,通过A能调用并访问到B,那就说明A持有B的引用,或A就是B的引用。

  Java中对引用的分类:

  

  在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

  软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

常见的内存泄露情况:

1、集合类泄露

  集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

2、非静态内部类创建静态实例造成内存泄漏。

  修复方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,推荐的使用Application 的 Context。

  匿名内部类有可能会持有当前Activity实例,如果将这个引用传入到异步线程,此线程和Activity生命周期不一致是,将会造成内存泄露。

3、Handler 造成的内存泄漏

  Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有,且Handler的声明周期和Activity的不一致,容易导致内存无法被正确释放。

  修复方法:在Activity中避免使用非静态内部类,即将Handler改为static,此时Handler的存活周期将于Activity无关,同事以弱应用的方式引入Activity,避免直接将Activity作为context传入。每次使用前需判空。 

  Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。

  1. public class MainActivity extends AppCompatActivity {
  2. private MyHandler mHandler = new MyHandler(this);
  3. private TextView mTextView ;
  4. private static class MyHandler extends Handler {
  5. private WeakReference reference;
  6. public MyHandler(Context context) {
  7. reference = new WeakReference<>(context);
  8. }
  9. @Override
  10. public void handleMessage(Message msg) {
  11. MainActivity activity = (MainActivity) reference.get();
  12. if(activity != null){
  13. activity.mTextView.setText("");
  14. }
  15. }
  16. }
  17.  
  18. @Override
  19. protected void onCreate(Bundle savedInstanceState) {
  20. super.onCreate(savedInstanceState);
  21. setContentView(R.layout.activity_main);
  22. mTextView = (TextView)findViewById(R.id.textview);
  23. loadData();
  24. }
  25.  
  26. private void loadData() {
  27. //...request
  28. Message message = Message.obtain();
  29. mHandler.sendMessage(message);
  30. }
  31.  
  32. @Override
  33. protected void onDestroy() {
  34. super.onDestroy();
  35. mHandler.removeCallbacksAndMessages(null);
  36. }
  37. }

4、尽量避免使用 static 成员变量

  如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。将会导致app常驻内存,从而导致app频繁重启,造成耗电、耗流量。

  修复方法:不要在类初始时初始化静态成员。可以考虑lazy初始化。

5、避免 override finalize()

  finalize 方法被执行的时间不确定,finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,含有Finalize方法的object需要至少经过两轮GC才有可能被释放。

6、线程泄露

  线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。  

6、资源未关闭造成的内存泄漏

  应在Activity被销毁时及时关闭或注销BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源。

7、在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

8、单例造成的内存泄露

  由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,则导致这个对象不能被正常回收,造成内存泄露。

  解决办法:

  1. public class AppManager {
  2. private static AppManager instance;
  3. private Context context;
  4. private AppManager(Context context) {
  5. this.context = context.getApplicationContext();// 使用Application 的context
  6. }
  7. public static AppManager getInstance(Context context) {
  8. if (instance != null) {
  9. instance = new AppManager(context);
  10. }
  11. return instance;
  12. }
  13. }

  或者以下写法,连Context都不用传进来了

  1. ...
  2.  
  3. context = getApplicationContext();
  4.  
  5. ...
  6. /**
  7. * 获取全局的context
  8. * @return 返回全局context对象
  9. */
  10. public static Context getContext(){
  11. return context;
  12. }
  13.  
  14. public class AppManager {
  15. private static AppManager instance;
  16. private Context context;
  17. private AppManager() {
  18. this.context = MyApplication.getContext();// 使用Application 的context
  19. }
  20. public static AppManager getInstance() {
  21. if (instance != null) {
  22. instance = new AppManager();
  23. }
  24. return instance;
  25. }
  26. }

检测程序中内存泄露的工具:LeakCanary、MAT等

一些建议

1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext

2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏

3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:  

  (1)将内部类改为静态内部类

  (2)静态内部类中使用弱引用来引用外部类的成员变量

6、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

参考文献:

  http://dev.qq.com/topic/59152c9029d8be2a14b64dae

Android内存泄露总结的更多相关文章

  1. (转)专项:Android 内存泄露实践分析

    今天看到一篇关于Android 内存泄露实践分析的文章,感觉不错,讲的还算详细,mark到这里. 原文发表于:Testerhome: 作者:ycwdaaaa ;  原文链接:https://teste ...

  2. Android内存泄露

    Android 内存泄漏是一个十分头疼的事情.LeakCanary是一款开源软件,主要作用是检测 Android APP 内存泄露.比起以前的 MAT 工具,LeakCanary 有着十分强大的功能, ...

  3. android内存泄露调试,Heap,MAT

    三.内存监测工具 DDMS --> Heap 无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方.Android tools中 ...

  4. Android内存泄露测试

    Android性能测试过程中的一些常用命令: CPU: adb shell top -n | grep "+PackageName 内存: adb shell dumpsys meminfo ...

  5. android内存泄露小谈

    在做android的时候,用的语言大部分情况下都是java.以前最开始做的是编译器开发, 大部分情况都是用c语言和x86与arm架构的汇编,后来接触到ios用的是OC.对比之下, 感觉还是java用起 ...

  6. android 内存泄露之jni local reference table overflow (max=512)

    在android项目中要实现一个需求 为了性能的要求只能用c代码来实现功能. 这样就牺牲了java跨平台性. 通过加载.so的方式,把用c实现的模块集成到app中. android提供jni层,作为一 ...

  7. Android内存泄露---检测工具篇

    内存使用是程序开发无法回避的一个问题.如果我们毫不在意肆意使用,总有一天会为此还账,且痛不欲生...所以应当防患于未然,把内存使用细化到平时的每一行代码中. 内存使用概念较大,本篇先讲对已有app如何 ...

  8. Android内存泄露的原因

    (一)释放对象的引用,误将一个本来生命周期短的对象存放到一个生命周期相对较长的对象中,也称“对象游离“.隐蔽的内部类(Anonymous Inner Class): mHandler = new Ha ...

  9. JVM内存管理概述与android内存泄露分析

    一.内存划分 将内存划分为六大部分,分别是PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量池以及本地方法栈. 1.PC寄存器(线程独有):全称是程序计数寄存器,它记载着每一个线程当前运行的 ...

  10. Android 内存泄露测试数据处理--procrank,setprop,getprop(转)

    1.Android内存测试常用的几个概念. VSS--virtual set size 虚拟耗用内存(包含共享库占用的内存)RSS--Resident set size实际使用的物理内存(包含共享库占 ...

随机推荐

  1. pulltoRefresh类图

  2. android mvp RxJava 框架结构分析

    一个MVP结构:M是model,V是Fragment,P是提供者,P持有V和Model,控制获取数据并给V赋值.(结合了RXJava Retrofit和okHttp)

  3. Python的介绍及Pycharm软件的安装

    一.Python介绍 1.  Python是一种解释性.面向对象.动态数据类型的高级程序设计语言. Python语言创始人是吉多.范罗苏姆:起源与1989年 2.  缺点:运行速度慢(由于是解释性语言 ...

  4. 继承users表,添加新字段成一个新表

    1. Tools > Run manage.py Task 创建app,users startapp users 2.修改users中的models from django.db import ...

  5. 全新Wijmo5中文学习指南正式上线

    Wijmo 是一款使用 TypeScript 编写的新一代 JavaScript/HTML5 控件集.它秉承触控优先的设计理念,在全球率先支持 AngularJS,并且支持React.VueJS以及T ...

  6. GitHub原来也可以用SVN客户端的.

    不知道是不是自己真的太宅了. 一直以为只能用git client 来clone github工程的.  偶然今日发现还可以用 SVN 来下载的. 果断一试. 太好用了. 这回windows 上不用纠结 ...

  7. IOS开发 警告 All interface orientations must be supported unless the app requires full screen.

    在IOS开发中遇到警告  All interface orientations must be supported unless the app requires full screen. 只要勾上R ...

  8. es6模块化开发

    export导出 import导入   export {a:b} Export default {a:b}

  9. js之侧边栏分享

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  10. 使用pjax时点击浏览器刷新按钮仅加载内容页的解决办法

    pjax可以实现ajax的局部刷新功能,同时能改变地址栏的URL,因此支持浏览器的后退和前进功能. 但是,在使用中,若没有正确使用,仍然会出现一些问题. 比如,我们在使用pjax后,能够在不加载整个页 ...