Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android的内存泄露

Android的内存泄露

作者头像
acc8226
发布于 2022-05-17 12:27:31
发布于 2022-05-17 12:27:31
1.3K00
代码可运行
举报
文章被收录于专栏:叽叽西叽叽西
运行总次数:0
代码可运行

对于开发老手,这个问题想必已经深入你的心;若是一名新手或者一直对内存泄漏这个东西模模糊糊的工程师,你的答案可能让面试官并不满意,这里将从底到上对内存泄漏的原因、排查方法和一些经验为你做一次完整的解剖。

处理内存泄漏的问题是将软件做到极致的一个必须的步骤,尤其是那种将被用户高强度使用的软件。

案例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class PendingOrderManager {
    private static PendingOrderManager instance;
    private Context mContext;

    public PendingOrderManager(Context context) {    
        this.mContext = context;
    }

    public static PendingOrderManager getInstance(Context context) {
        if (instance == null) {
            instance = new PendingOrderManager(context);
        }
        return instance;
    }
   public void func(){
     ...
    }

...
}

然后让你的某个Activity去使用这个PendingOrderManager单例,并且某个时候退出这个Activity:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//belong to some Activity
PendingOrderManager.getInstance(this).func();

...
finish()

这个时候内存泄漏已经发生:你退出了你的这个Activity本以为java的垃圾回收会将它释放,但实际上Activity一直被PendingOrderManager持有着。Acitivity这个Context被长生命周期个体(单例一旦被创建就是整个app的生命周期)持有导致了这个Context发生了内存泄漏。

这个例子和上面的例子是相通的,上面的C的例子因为忘记了手动执行free一个10字节内存导致内存泄漏。而下面这个例子是垃圾回收机制“故意忘记”了回收Context的内存而导致了内存泄漏。下面两节将对这个里面到底发生了什么进行说明。

静态、堆和栈

编译原理说软件内存分配的时候一般会放在三种位置:静态存储区域、堆和栈,他们的位置、功能、速度都各不相同,区别如下:

  • 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局static数据和常量
  • 栈:就是CPU的寄存器(并不是内存),特点是容量很小但是速度最快,函数或者方法的的方法体内声明的变量或者指向对象的引用、局部变量即分配在这里,生命周期到该函数或者方法体尾部即止
  • 堆:就是动态内存分配去(就是实体的内存RAM),C中malloc和fee,java中的new和垃圾回收直接操作的就是这里的区域,类的成员变量分配在这里 从上面即可看出静态存储区域是编译时已经分配好的,栈是CPU自动控制的,那么我们所讨论的内存泄漏的问题实际上就是分配在堆里面的内存出现了问题,一般问题在于两点:
  1. 快速不断的进行new操作。比如Android的自定义View的时候你在onDraw里面new出对象,就会导致这个自定义View的绘制特别卡,这是因为onDraw是快速重复执行的方法,在这个方法里面每次都new出对象会导致连续不断的new出新的对象,也导致gc也在不断的执行从而不断的回收堆内存。由于堆位于内存RAM上,这样子就导致了内存的不断的分配和回收消耗了CPU,同时导致了内存出现“空洞”(因为堆内存不是连续的)
  2. 忘记释放。如果你忘记了手动释放应该释放的内存,或者gc误判导致没有释放本应该释放的内存,那么久导致了内存泄漏。由于Android给一个app可在堆上(可以在AndroidManifest设置一个largeHeap="true"增大可分配量)分配的内存量是有限的,如果内存泄漏不断的发生,总有一天会消耗完毕,从而导致OOM

Java有了垃圾回收(GC)为什么依然会内存泄漏

在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。但是误判是经常发生的,有些内存实际上已经没有用处了,但是GC并不知道。这里简单介绍下GC的机制:

上面一节说过栈上的局部变量可以引用堆上的分配的内存,所以GC发生的时候,一般是遍历一下静态存储区、栈从而列出所有堆上被他们引用的内存(对象)集合,这些内存都是有个引用计数,那么除此之外,其他的内存就是没有被引用的(或者说引用计数归零),这些内存就是要被释放的,随后GC开始清理这些内存(对象)

那么这里第一节的两个例子就很好理解了,那个单例模式由于生命周期太长(可以把他看作一个虚拟的栈中的局部变量)并且一直引用了Context(即Activity),所以GC的时候发现这个Activity的引用计数还是大于1,所以回收内存的时候把他跳过,但实际上我们已经不需要这块内存了。这样就导致了内存泄漏。

Android使用弱引用和完美退出app的方法

从上面来看,内存泄漏因为对象被别人引用了而导致,java为了避免这种问题(假如你的单例模式必须要传入个Context),特地提供了几个特殊引用类型,其中一个叫做弱引用WeakReference,当它引用一个对象的时候,即使该WeakReference的生命周期更长,但是只要发生GC,它就立即释放所被引用的内存而不会继续持有。

这里有一个常用的例子:

通常我们会在自定义的Application中来记住app中创建的Activity,从而中途在某个Activity中需要完全退出app时可以完全的销毁所有已经打开的Activity,这里我们可以对自定义Application改造,让其只有一个对Activity的弱引用的HashMap,大致的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CustomApplication extends Application {

    private HashMap<String, WeakReference<Activity>> activityList = new HashMap<String, WeakReference<Activity>>();
    private static CustomApplication instance;

    public static CustomApplication getInstance() {
        return instance;
    }

    public void addActivity(Activity activity) {
        if (null != activity) {
            L.d("********* add Activity " + activity.getClass().getName());
            activityList.put(activity.getClass().getName(), new WeakReference<>(activity));
        }
    }


    public void removeActivity(Activity activity) {
        if (null != activity) {
            L.d("********* remove Activity " + activity.getClass().getName());
            activityList.remove(activity.getClass().getName());
        }
    }


    public void exit() {
        
        for (String key : activityList.keySet()) {
            WeakReference<Activity> activity = activityList.get(key);
            if (activity != null && activity.get() != null) {
                L.d("********* Exit " + activity.get().getClass().getSimpleName());
                activity.get().finish();
            }
        }

        System.exit(0);
        android.os.Process.killProcess(android.os.Process.myPid());
    }


}

我们在自定义的Activity的基类BaseActivity中的onCreate执行: CustomApplication.getInstance().addActivity(this);

在BaseActivity的onDestroy中执行: CustomApplication.getInstance().removeActivity(this);

哪些情况会导致内存泄漏

到此你应该对内存泄漏的本质已经有所了解了,这里列举出一些会导致内存泄漏的地方,可以作为排查内存泄漏的一个checklist

  • 某个集合类(List)被一个static变量引用,同时这个集合类没有删除自己内部的元素
  • 单例模式持有外部本应该被释放的对象(第一节中那个例子)
  • Android特殊组件或者类忘记释放,比如:BraodcastReceiver忘记解注册、Cursor忘记销毁、Socket忘记close、TypedArray忘记recycle、callback忘记remove。如果你自己定义了一个类,最好不要直接将一个Activity类型作为他的属性,如果必须要用,要么处理好释放的问题,要么使用弱引用
  • Handler。只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。如上所述,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。
  • Thread。如果Thread的run方法一直在循环的执行不停,而该Thread又持有了外部变量,那么这个外部变量即发生内存泄漏。
  • 网络请求或者其他异步线程。之前Volley会有这样的一个问题,在Volley的response来到之前如果Activity已经退出了而且response里面含有Activity的成员变量,会导致该Activity发生内存泄漏,该问题一直没有找到合适的解决办法。不过看来Volley官网已经注意到这个问题了,目前最新的版本已经fix this leak

使用leakcanary

之前Android开发通常使用MAT内存分析工具来排查heap的问题,之类的文章比较多,大家可以自己找。这里推荐一个叫做leakcanary的工具,他可以集成在你的代码里面。这个东西大家可以参考: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0509/2854.html

特别鸣谢

文章改编于: 内存泄漏弄个明白 - soaringEveryday - 博客园 http://www.cnblogs.com/soaringEveryday/p/5035366.html

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
内存泄露的一些坑
如上,在Activity内部如果声明一个这样的Handler,那么myHandler就默认持有Activity引用,假设Activity退出了,但是可能这时候才有myHandler的任务post,那么Activity是无法被回收的,可以采用以下方式解决:
大大大大大先生
2018/09/04
1.8K0
最常见的Android内存优化方式及防止泄漏造成OOM总结篇
内存优化目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。既然说到内存泄漏和优化,就不得不先简单了解一下内存分配策略,然后再举常见泄漏例子和解决方法,最后做一下总结,这样更直观全面了解Android内存方面处理。
Android技术干货分享
2019/10/08
1.3K0
内存泄露从入门到精通三部曲之基础知识篇
1 首先以一个内存泄露实例来开始本节基础概念的内容: 实例1:(单例导致内存对象无法释放而泄露) 可以看出ImageUtil这个工具类是一个单例,并引用了activity的context。 试想这个场景,应用起来以后,转屏。转屏以后,旧MainActivity会destroy,新MainActivity会重建,导致单例ImageUtil重新getInstance。很不幸的是,由于instance已经不是空的了,所以ImageUtil不会重建,还持有之前的Context,也就是之前的那个MainAc
腾讯Bugly
2018/03/23
1.3K0
内存泄露从入门到精通三部曲之基础知识篇
Android内存泄露和ANR
内存泄漏(Memory Leak)是指程序在运行过程中,由于疏忽或错误未能释放不再使用的内存,导致这部分内存无法被回收,最终可能引发应用卡顿、崩溃或系统性能下降。
进击的阿斌
2025/02/13
3490
Android内存泄漏分析
强引用:类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
用户1205080
2019/03/06
1.6K0
Android内存泄漏分析
避免Android中Context引起的内存泄露
Context是我们在编写Android程序经常使用到的对象,意思为上下文对象。 常用的有Activity的Context还是有Application的Context。Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源。在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题。
技术小黑屋
2018/09/05
1.4K0
Android常见内存泄露,学会这六招大大优化APP性能
很多开发者都知道,在面试的时候会经常被问到内存泄露和内存溢出的问题。 内存溢出(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小。 内存泄
分享达人秀
2018/02/02
1.2K0
Android常见内存泄露,学会这六招大大优化APP性能
Android 关于内存泄露,你必须了解的东西
内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题。内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」。
developerHaoz
2018/08/20
1.2K0
内存泄漏三问—vivo真题
说到性能优化,就不得不提下内存泄漏了,内存泄漏发生的原因以及解决办法你是否都已了解呢?看看今天的三问:
码上积木
2020/09/27
6000
Java中关于内存泄漏出现的原因以及如何避免内存泄漏(超详细版汇总上)
内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量。
天涯泪小武
2021/12/09
4.3K2
Java中关于内存泄漏出现的原因以及如何避免内存泄漏(超详细版汇总上)
Android性能优化之内存泄漏,你想要的这里都有~
在Android中,内存泄露的现象十分常见;而内存泄露导致的后果会使得应用Crash 本文 全面介绍了内存泄露的本质、原因 & 解决方案,最终提供一些常见的内存泄露分析工具,希望你们会喜欢。
用户9239674
2022/02/09
8830
Android 进阶解密笔记-热修复
内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
Yif
2019/12/26
4190
Android开发:详解Handler的内存泄露
上面提到,在Java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。 但是,静态内部类不会。
Carson.Ho
2019/02/22
1.2K1
Android 内存泄漏总结
内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量。
阳仔
2019/07/31
6240
Android 内存泄漏总结
最常见的8个Android内存泄漏问题及解决方法
在 Android 开发中,内存泄漏是一个常见的问题。这个问题可能会导致应用程序变慢、崩溃或者消耗大量的内存,最终导致设备性能下降。
Rouse
2023/08/31
1.1K0
最常见的8个Android内存泄漏问题及解决方法
Android开发中应该避免的内存泄露
一、背景和目的: 目前许多开发人员在Android开发过程中,较少关注实现细节和内存使用,容易会造成内存泄露,导致程序OOM。 本文会通过代码向大家介绍在Android开发过程中常见的内存泄露。 二、常见的内存泄露代码 1、使用Handler****造成的内存问题 在Android开发过程中,Handler是比较常用的,通过Handler发送Message与主线程进行通信,Message发送之后是存储在MessageQueue中的,有些Message并不是马上被处理的,在Message中存在一个Target
陈仁松
2018/03/20
1.3K0
Android开发从GC root分析内存泄漏
我们常说的垃圾回收机制中会提到GC Roots这个词,也就是Java虚拟机中所有引用的根对象。我们都知道,垃圾回收器不会回收GC Roots以及那些被它们间接引用的对象。但是,对于GC Roots的定义却不是很清楚。它们都包括哪些对象呢?
yuxiaofei93
2018/09/11
2.2K0
Android经典面试题之开发中常见的内存泄漏,以及如何避免和防范
内存泄漏(Memory Leak)在软件开发中指的是程序未能释放已不再需要的内存,从而导致内存的浪费。在Android应用开发中,内存泄漏尤其重要,因为移动设备通常内存较为有限,长期的内存泄漏会导致应用变慢、崩溃,甚至影响整个系统的稳定性。
AntDream
2024/08/06
1230
Android经典面试题之开发中常见的内存泄漏,以及如何避免和防范
Android 内存泄露简介、典型情景及检测解决
什么是内存泄露? Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。 内存泄露的经典场景 非静态
非著名程序员
2018/02/02
8150
Android 内存泄露简介、典型情景及检测解决
Android--关闭某个指定activity
最近项目中有这样的需要,在关闭当前Activity同时关闭前面两个Activity,不涉及到应用的退出。自己想了一些方案,也查了一些资料,做个笔记吧。
Android技术干货分享
2019/04/17
5.3K0
Android--关闭某个指定activity
推荐阅读
相关推荐
内存泄露的一些坑
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验