Android 内存泄漏的几种可能总结

Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全。

不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM)。

一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放。(译者注:Cursor忘记关闭等)

逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,当仍未释放该对象的所有引用。

如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。

在Android开发中,最容易引发的内存泄漏问题的是Context。比如Activity的Context,就包含大量的内存引用,例如View Hierarchies和其他资源。一旦泄漏了Context,也意味泄漏它指向的所有对象。Android机器内存有限,太多的内存泄漏容易导致OOM。

检测逻辑内存泄漏需要主观判断,特别是对象的生命周期并不清晰。幸运的是,Activity有着明确的生命周期,很容易发现泄漏的原因。Activity.onDestroy()被视为Activity生命的结束,程序上来看,它应该被销毁了,或者Android系统需要回收这些内存(译者注:当内存不够时,Android会回收看不见的Activity)。

如果这个方法执行完,在堆栈中仍存在持有该Activity的强引用,垃圾回收器就无法把它标记成已回收的内存,而我们本来目的就是要回收它!
结果就是Activity存活在它的生命周期之外。

Activity是重量级对象,应该让Android系统来处理它。然而,逻辑内存泄漏总是在不经意间发生。(译者注:曾经试过一个Activity导致20M内存泄漏)。在Android中,导致潜在内存泄漏的陷阱不外乎两种:

1.全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。

2.活在Activity生命周期之外的线程。没有清空对Activity的强引用。

检查一下你有没有遇到下列的情况。

Static Activities

在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

  static Activity activity;

  void setStaticActivity() {
   activity = this;
  }

  View saButton = findViewById(R.id.sa_button);
  saButton.setOnClickListener(new View.OnClickListener() {
   @Override public void onClick(View v) {
    setStaticActivity();
    nextActivity();
   }
  });

Memory Leak 1 – Static Activity

Static Views

类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。

特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)

  static view;

  void setStaticView() {
   view = findViewById(R.id.sv_button);
  }

  View svButton = findViewById(R.id.sv_button);
  svButton.setOnClickListener(new View.OnClickListener() {
   @Override public void onClick(View v) {
    setStaticView();
    nextActivity();
   }
  });

Memory Leak 2 – Static View

Inner Classes

继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。

 private static Object inner;

    void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
  }

  View icButton = findViewById(R.id.ic_button);
  icButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
      createInnerClass();
      nextActivity();
    }
  });

Memory Leak 3 – Inner Class

内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。

Anonymous Classes

相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk
。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。

 void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
      @Override protected Void doInBackground(Void... params) {
        while(true);
      }
    }.execute();
  }

  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  View aicButton = findViewById(R.id.at_button);
  aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
      startAsyncTask();
      nextActivity();
    }
  });

Memory Leak 4 – AsyncTask

Handler

同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

 void createHandler() {
    new Handler() {
      @Override public void handleMessage(Message message) {
        super.handleMessage(message);
      }
    }.postDelayed(new Runnable() {
      @Override public void run() {
        while(true);
      }
    }, Long.MAX_VALUE >> 1);
  }

  View hButton = findViewById(R.id.h_button);
  hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
      createHandler();
      nextActivity();
    }
  });

Memory Leak 5 – Handler

Threads

我们再次通过Thread和TimerTask来展现内存泄漏。

  void spawnThread() {
    new Thread() {
      @Override public void run() {
        while(true);
      }
    }.start();
  }

  View tButton = findViewById(R.id.t_button);
  tButton.setOnClickListener(new View.OnClickListener() {
   @Override public void onClick(View v) {
     spawnThread();
     nextActivity();
   }
  });

Memory Leak 6 – Thread

TimerTask

只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。

 void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
      @Override
      public void run() {
        while(true);
      }
    }, Long.MAX_VALUE >> 1);
  }

  View ttButton = findViewById(R.id.tt_button);
  ttButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
      scheduleTimer();
      nextActivity();
    }
  });

Memory Leak 7 – TimerTask

Sensor Manager

最后,通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。

  void registerListener() {
        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
    }

    View smButton = findViewById(R.id.sm_button);
    smButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        registerListener();
        nextActivity();
      }
    });

Memory Leak 8 – Sensor Manager

总结

看过那么多会导致内存泄漏的例子,容易导致吃光手机的内存使垃圾回收处理更为频发,甚至最坏的情况会导致OOM。垃圾回收的操作是很昂贵的开销,会导致肉眼可见的卡顿。所以,实例化的时候注意持有的引用链,并经常进行内存泄漏检查。

以上就是对Android 内存泄漏的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

(0)

相关推荐

  • 浅谈Android应用的内存优化及Handler的内存泄漏问题

    一.Android内存基础 物理内存与进程内存 物理内存即移动设备上的RAM,当启动一个Android程序时,会启动一个Dalvik VM进程,系统会给它分配固定的内存空间(16M,32M不定),这块内存空间会映射到RAM上某个区域.然后这个Android程序就会运行在这块空间上.Java里会将这块空间分成Stack栈内存和Heap堆内存.stack里存放对象的引用,heap里存放实际对象数据. 在程序运行中会创建对象,如果未合理管理内存,比如不及时回收无效空间就会造成内存泄露,严重的话可能导致

  • Android性能优化之利用强大的LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来.所以决定抽空学习总结一下这方面的知识,以及分享一下我们是如何检测内存泄漏的.我们公司使用开源框架LeakCanary来检测内存泄漏. 什么是内存泄漏? 有些对象只有有限的生命周期.当它们的任务完成之后,它们将被垃圾回收.如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏

  • Android 有效的解决内存泄漏的问题实例详解

    Android 有效的解决内存泄漏的问题 Android内存泄漏,我想做Android 应用的时候遇到的话很是头疼,这里是我在网上找的不错的资料,实例详解这个问题的解决方案 前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的作用. 本文的内存泄漏检测工具是:LeakCanary  github地址:https://github.com/square/leakcanary 什么是内存泄漏? 内存泄漏

  • Android开发:浅谈MVP模式应用与内存泄漏问题解决

    最近博主开始在项目中实践MVP模式,却意外发现内存泄漏比较严重,但却很少人谈到这个问题,促使了本文的发布,本文假设读者已了解MVP架构. MVP简介 M-Modle,数据,逻辑操作层,数据获取,数据持久化保存.比如网络操作,数据库操作 V-View,界面展示层,Android中的具体体现为Activity,Fragment P-Presenter,中介者,连接Modle,View层,同时持有modle引用和view接口引用 示例代码 Modle层操作 public class TestModle

  • Android内存泄漏终极解决篇(下)

    一.概述 在 Android内存泄漏终极解决篇(上)中我们介绍了如何检查一个App是否存在内存泄漏的问题,本篇将总结典型的内存泄漏的代码,并给出对应的解决方案.内存泄漏的主要问题可以分为以下几种类型: 静态变量引起的内存泄漏 非静态内部类引起的内存泄漏 资源未关闭引起的内存泄漏 二.静态变量引起的内存泄漏 在java中静态变量的生命周期是在类加载时开始,类卸载时结束.换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束.所以在程序的运行期间,如果进程没有被杀死,静态变量就会一

  • Android内存泄漏排查利器LeakCanary

    本文为大家分享了Android内存泄漏排查利器,供大家参考,具体内容如下 开源地址:https://github.com/square/leakcanary 在 build.gralde 里加上依赖, 然后sync 一下, 添加内容如下 dependencies { .... debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanar

  • Android内存泄漏终极解决篇(上)

    一.概述 在Android的开发中,经常听到"内存泄漏"这个词."内存泄漏"就是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能被回收."内存泄漏"的慢慢积累,最终会导致OOM的发生,千里之堤,毁于蚁穴.所以在写代码的过程中,应该要注意规避会导致"内存泄漏"的代码写法,提高软件的健壮性. 本文将从发现问题.解决问题.总结问题的三个角度出发,循序渐进,彻底解决"内存泄漏"的问题

  • 使用Android Studio检测内存泄露(LeakCanary)

    内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁千里之堤的蚁穴. 怎么才能检测内存泄露呢? AndroidStudio 中Memory控件台(显示器)提供了一个内存监视器.我们可以通过它方便地查看应用程序的性能和内存使用情况,从而也就可以找到需要释放对象,查找内存泄漏等. 熟悉Memory界面 打开日志控制台,有一个标签Memory ,我们可以在这个界面分析当前程序使用的内存情况. 运行要监控的程序(APP)后,打开Android Monitor控制台窗口,可以看到

  • Android 内存泄漏的几种可能总结

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 一般内存泄漏(traditional memory leak)的原因

  • 分析Android内存泄漏的几种可能

    前言 内存泄漏简单地说就是申请了一块内存空间,使用完毕后没有释放掉.它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃.由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了. 从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在.真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存.从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内

  • Android内存泄漏的原因及解决技巧

    正确的生命周期管理如何防止Android内存泄漏 OutOfMemoryException是一个常见的令人沮丧的错误,也是导致应用程序意外关闭的主要原因之一. "如果应用程序昨天运行良好,为什么现在会发生这种情况?这个问题让Android的开发者和新手都感到困惑. 导致OutOfMemory异常的潜在原因有很多种,但其中最常见的是内存泄漏-应用程序中的内存分配从未释放.本文将解释如何通过有效的生命周期管理(开发过程中一个重要但经常被忽视的部分)来最小化这种风险. 为什么安卓系统会发生内存泄漏?

  • JS造成内存泄漏的几种情况实例分析

    本文实例讲述了JS造成内存泄漏的几种情况.分享给大家供大家参考,具体如下: 介绍: js中的内存垃圾回收机制:垃圾回收器会定期扫描内存,当某个内存中的值被引用为零时就会将其回收.当前变量已经使用完毕但依然被引用,导致垃圾回收器无法回收这就造成了内存泄漏.传统页面每次跳转都会释放内存,所以并不是特别明显. Vue单页面应用中:Web App 与 传统Web的区别,因为Web App是单页面应用页面通过路由跳转不会刷新页面,导致内存泄漏不断堆积,导致页面卡顿. 泄漏点: 1.DOM/BOM 对象泄漏

  • golang容易导致内存泄漏的6种情况汇总

    目录 1. 定时器使用不当 1.1 time.After()的使用 1.2 time.NewTicker资源未及时释放 2. select阻塞 2.1 导致goroutine阻塞的情况 2.2 循环空转导致CPU暴涨 3. channel阻塞 4. goroutine导致的内存泄漏 4.1 申请过多的goroutine 4.2 goroutine阻塞 4.2.1 I/O问题 4.2.2 互斥锁未释放 4.2.3 死锁 4.2.4 waitgroup使用不当 5. slice 引起的内存泄漏 6.

  • Android内存泄漏实战解析

    Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全. 不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak).如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM). 1.一般内存泄漏(traditional memory leak)的

  • Android内存泄漏的轻松解决方法

    前言 内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题.内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」. 如果程序发生了内存泄露,则会带来以下这些问题 应用可用的内存减少,增加了堆内存的压力 降低了应用的性能,比如会触发更频繁的 GC 严重的时候可能会导致内存溢出错误,即 OOM Error 下面我们从基础说起 基础知识 Java 的内存分配简述 方法区(non-heap):编译时就分配好,在程序整个运行期间都存在.它主要存放静

随机推荐