专栏首页俞其荣的博客闲谈Android中的内存泄漏

闲谈Android中的内存泄漏

Part 1

在长久以来的 Android 开发过程中,内存泄漏一直是一个比较头疼的问题。内存泄漏会导致应用卡顿,用户体验不佳,甚至会造成应用崩溃的严重后果。所以如何科学地进行内存管理一直是大家探讨的话题,从一开始主动使用 MAT 分析 hprof 文件,到后来 LeakCanary “被动”的接收内存泄漏消息。应用中发现内存泄漏的手段越来越多了,操作也越来越便捷,但内存泄漏的问题还是不能轻易忽视的,提高应用的体验和质量也是迫在眉睫。

那今天,就从最基本的开始聊聊内存泄漏。

Part 2

内存泄漏简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。我们所说的内存泄露是针对于堆内存而言,堆内存中存放的就是引用指向的对象实体。

在这里先科普下内存分配的三种策略。

  • 静态的,使用的内存空间是静态存储区
  • 栈式的,使用的内存空间是栈区
  • 堆式的,使用的内存空间是堆区

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

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

堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存(Java则依赖垃圾回收器)。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉。 接下来我们集中说下堆和栈的区别:

在函数中(说明是局部变量)定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

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

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

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

说了这么多了,我们来看一个例子吧:

class Student {
    private int age = 10;
    private School school = new School();
    
    public void doHomework() {
        Book book = new Book();
        int pageNo = 15;
    }
}

Student s = new Student();

s 自己存放在栈中,而 s 指向的对象实体存放在堆中;

其中 s 这个对象实体中的全局变量 age 和 school 都是存放在堆中(包括基本数据类型、引用和引用的对象实体)

doHomework 中的引用变量 book 和局部变量 pageNo 是存放在栈中的,而引用变量 book 指向的对象是存放在堆中的。

局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。 成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。

Part 3

那么有没有想过,内存为什么会泄露?

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

在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。

GC过程与对象的引用类型是严重相关的,我们来看看Java对引用的分类Strong reference, SoftReference, WeakReference, PhatomReference

20190629134830.png

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

如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

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

Part 4

Android中常见的内存泄漏问题:

  • 单例造成的内存泄露
  • InnerClass匿名内部类
  • Activity Context 的不正确使用
  • Handler引起的内存泄漏
  • 注册监听器的泄漏
  • Cursor,Stream没有close,View没有recyle
  • 集合中对象没清理造成的内存泄漏
  • WebView造成的泄露
  • 构造Adapter时,没有使用缓存的ConvertView

具体可以参考 Android内存泄漏分析心得

Part 5

Android 中检测内存泄漏的工具

  • MAT
  • Android Profiler
  • LeakCanary

Part 6

参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Glide源码解析(一)

    Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline...

    俞其荣
  • Tinker源码分析(六):补丁合成流程

    下发的补丁包其实并不能直接加载,因为补丁包只是差异包,需要和本地的 dex 、资源等进行合成后,得到全量的 dex 才能被完整地使用。这样也就避免了热修复中 d...

    俞其荣
  • LeakCanary源码解析

    LeakCanary : https://github.com/square/leakcanary

    俞其荣
  • 【iOS面试粮食】内存管理

    iOS的内存管理一般指的是OC对象的内存管理,因为OC对象分配在堆内存,堆内存需要程序员自己去动态分配和回收;基础数据类型(非OC对象)则分配在栈内存中,超过作...

    编程怪才-凌雨画
  • 剖析 Python 面试知识点(二)- 内存管理和垃圾回收机制

    Python 中一切皆对象,对象又可以分为可变对象和不可变对象。二者可以通过原地修改,如果修改后地址不变,则是可变对象,否则为不可变对象,地址信息可以通过id(...

    天澄技术杂谈
  • JavaScript的工作原理:内存管理+如何处理4个常见的内存泄漏

    这篇文章将讨论日常编程中另一个复杂且容易被忽视的问题 — 内存管理。其中还提供了一些关于如何处理 JavaScript 内存泄露的提示,来防止导致内存泄漏以及不...

    奋飛
  • 内存泄露从入门到精通三部曲之基础知识篇

    1 首先以一个内存泄露实例来开始本节基础概念的内容: 实例1:(单例导致内存对象无法释放而泄露) ? ? 可以看出ImageUtil这个工具类是一个单例,并引...

    腾讯Bugly
  • Java内存泄漏介绍

    内存管理是Java最重要的优势之一,你只需创建对象,Java垃圾收集器会自动负责分配和释放内存。但是,情况并不那么简单,因为在Java应用程序中经常发生内存泄漏...

    Java技术栈
  • 什么是Python的 “内存管理机制”

    Python作为一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言,与大多数编程语言不同,Python中的变量无需事先申明,变量无需指定类型,程序员无...

    小灰
  • Android内存泄漏分享

    内容概述 内存泄漏和内存管理相关基础。 Android中的内存使用。 内存分析工具和实践。 以下内容不考虑非引用类型的数据,或者将其等同为对应的引用类型看待——...

    用户1172465

扫码关注云+社区

领取腾讯云代金券