Android中内存泄漏超级精炼详解

标签: 内存泄漏  内存溢出  常见原因


一、前期基础知识储备

(1)什么是内存?

JAVA是在JVM所虚拟出的内存环境中运行的JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。


(stack):是简单的数据结构,但在计算机中使用广泛。栈最显著的特征是:LIFO(Last In, First Out, 后进先出)。后来者居上。(跟线程中队列的顺序恰好相反)栈中只存放基本类型对象的引用(不是对象)。

(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身


方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量

(2)什么是内存泄漏?

提及内存泄露,就不得不提到内存溢出,这两个比较容易混淆的概念,我们来分析一下。

内存泄露:ML (Memory Leak),程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。

内存泄漏对程序的影响是:容易使得应用程序发生内存溢出,即 OOM


内存溢出:OOM(Out of Memory),程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现OOM。小明向他同桌小红要橡皮,周一要一块,用一半没还;周二要一块,用一半没还;周三要一块,用一半没还;周四周五也是一样,然后到下周一他就失去这个同桌了,并且被叫了家长。

内存溢出对程序的影响是:导致ANR错误甚至应用Crush。OOM错误最终会导致ANR错误,即应用程序无响应,经常无响应的应用程序用着是会有点“上火”,比如“炉石传说掌游宝”(捞钱倒是有一手,换头像10元起步),这个ANR错误真的是每次打开有会报错,有时候真的是十分的恼火。


二、Android内存泄漏原因分析

(1)内存泄漏原因分析

需掌握的概念:栈中只存放基本类型变量和对象的引用,栈(stack)可以自行清除不用的内存空间;栈中引用的对象的本体存放在堆中,但在JAVA中堆内存不会随着方法的结束而清空;所以如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽;

聪明的Java引入了垃圾回收(garbage collection)去处理堆内存的回收,perfect;

但是实际情况是,仍然有很多情况导致内存泄漏:如果对象一直被引用无法被回收,造成内存的浪费,无法再被使用。所以对象无法被GC回收就是造成内存泄露的原因!

(2)Java垃圾回收机制分析

垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。

回收机制分析:我们将栈定义为root,遍历栈中所有的对象的引用再遍历一遍堆中的对象。因为栈中的对象的引用执行完毕就删除,所以我们就可以通过栈中的对象的引用,查找到堆中没有被指向的对象,这些对象即为不可到达对象,对其进行垃圾回收。

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

————————————————————我是重点分隔线——————————————————

(3)内存泄漏的真实原因

内存泄露的直接原因是:本该回收的对象,因为某些原因(对象的强引用被另外一个正在使用的对象所持有,且没有及时释放),进而造成内存单元一直被占用,浪费空间,甚至可能造成内存溢出!

内存泄漏的本质原因是:持有引用者的生命周期>被引用者的生命周期!


强引用:实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。对于这类引用GC任何时候不会对其进行内存回收,在内存不足的情况下宁愿抛出Out of Memory(OOM内存溢出)。类似这样的都是强引用:

private final MessageReceived mMessageReceived=new MessageReceived(this);

————————————————————我是重点分隔线——————————————————

其实在Android中会造成内存泄露的情景无外乎两种

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

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


小结:虽然现在手机内存在不停的提升,内存泄露兴许不会像dalvik时代由于虚拟机内存过小造成各种花样OOM。但是过量的内存泄露依然会造成内存溢出,影响用户体验,在如今定制系统层出不穷、机型花样越来越多的情况下解决好内存泄露的问题会让适配和稳定性进一步提高!

三、Android中什么导致内存泄漏

有开发者总结了如下一张表,这里供读者参考:


资源对象没关闭造成的内存泄漏——描述: 资源性对象比如(Cursor,File文件,SQLiteDatabase)往往都用了一些缓冲,我们在不使用的时候或者在使用完之后,应该及时关闭它们比如close()方法,以便它们的缓冲及时回收内存。

构造Adapter时,没有使用缓存的convertView——常见于使用ListView,如果,不使用convertView,那么每次列表加载都会加载一次对象,用户反复加载,就会造成大量的对象创建,得不到释放,极容易发生内存泄漏。

public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}
修正示例代码:
public View getView(int position, ViewconvertView, ViewGroup parent) {
    View view = null;
        if (convertView != null) {
        view = convertView; 
        populate(view, getItem(position));
        ...
        } else {
                view = new Xxx(...);
                ...
    }
return view;
};

对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null ——有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。

对于生命周期比Activity长的对象如果需要应该使用ApplicationContext ——这是一个很隐晦的内存泄漏的情况。有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长,而不是取决于activity的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。

集合中对象没清理造成的内存泄漏——我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。

使用Service之后,没有关闭服务——如果Service停止失败也会导致内存泄漏。

因为系统会倾向于把这个Service所依赖的进程进行保留,如果这个进程很耗内存,就会造成内存泄漏。建议使用IntentService,可以自动停止。

内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在内存泄漏,当检测到程序中有内存泄漏的产生时,它将告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。

最后 祝各位开发者/读者少bug少泄漏少溢出少crash!!!

版权声明:本文为weixin_41101173原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41101173/article/details/79710782

智能推荐

ActiveMQ学习4-ActiveMQ的安全机制和集群模式

ActiveMQ的安全机制和集群模式 20 ActiveMQ安全机制 20.1 Web 控制台安全 20.2 消息服务器Broker安全 21 ActiveMQ主从集群 21.1 使用集群的重要性 20.2 主从集群的方式 20.2.1 shared filesystem Master-Slave方式主从集群 20.2.2 shared database Master-Slave方式主从集群 20...

说说 Python Django 应用的基础目录结构

通过以下 django-admin 指令创建应用之后,就会生成应用的基础目录结构。 比如,我们建立了一个叫 ‘first’ 的应用,它的目录结构是这样的: 目录或文件 说明 最外层的 first/ 这是新应用的根目录,所有与该应用相关的内容都放在这里。 manage.py 用于管理 Django 项目的命令行工具。 里面一层的 first/ 目录 是一个...

Springboot整合rabbitMQ

依赖: 配置文件application.yml RabbitConfig 消息生产者RabbitProducer 消息消费者RabbitCustomer 通过Controller进行调用 启动项目后调用接口: 结果:...

Thread.join()方法的使用

如果一个线程A执行了thread.join()语句,代表当前线程A等待thread线程终止后才从thread.join()方法返回 并且这个方法具有超时特性,可以添加参数设置 输出结果: jdk中Thread.join()方法的源码(进行了部门调整)   每个线程终止的条件是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,  当线程终止时,会调用自身的no...

linux服务器部署jenkins笔记

安装jenkins参考文档:https://blog.csdn.net/tomatocc/article/details/83930714 1. 打开jenkins官网:https://jenkins.io/download/ 将war包下载到本地 **ps:**这里要注意的是要下载左边下方的war包,不要下载右边下面的war包。左边是稳定版本,右边是最新版本,建议大家使用稳定版本(我刚开始下载的...

猜你喜欢

k8s部署elasticsearch集群

百度营销大学     环境准备 我们使用的k8s和ceph环境见: https://blog.51cto.com/leejia/2495558 https://blog.51cto.com/leejia/2499684 ECK简介 Elastic Cloud on Kubernetes,这是一款基于 Kubernetes Operator 模式的新型编排产品,用户可使用该产品在...

saas-export项目-AdminLTE介绍与入门

AdminLTE介绍 (1)AdminLTE是什么? AdminLTE是一款建立在bootstrap和jquery之上的开源的模板主题工具 (2)AdminLTE有什么特点? 提供一系列响应的、可重复使用的组件, 并内置了多个模板页面 自适应多种屏幕分辨率,兼容PC和移动端 快速的创建一个响应式的Html5网站 AdminLTE 不但美观, 而且可以免去写很大CSS与JS的工作量 AdminLTE...

MyBatis中ResultMap结果集映射

用于解决属性名和字段名不一致的情况: resultMap 元素是 MyBatis 中最重要最强大的元素。...

编写一个shell

编写shell的过程: 1.从标准输入中读入一个字符串。 2.解析字符串 3.创建一个子进程的执行程序。 4.子进程程序替换。 5.父进程等待子进程退出。...

WEB自动化测试中Xpath定位方法

前言: Xpath是在XML文档中查找信息的一种语言,使用路径表达式来选取XML文档中的节点或节点集,由于XML与HTML结构类似(前者用于传输数据,后者用于显示数据),所以Xpath也常用于查找HTML文档中的节点或节点集。 一  路径表达式: 路径以“/”开始     表示找到满足该绝对路径的元素; 路径以//”开始  ...