前言

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果。

那什么情况下不能被回收呢?

目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法。算法的基本思路是:通过一系列的名为 GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots 到这个对象不可达)时, 证明此对象是不可用的。

关于可达性的对象,便是能与 GC Roots 构成连通图的对象,如下图:

根搜索算法的基本思路就是通过一系列名为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链 ( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

从上图,reference1、reference2、reference3 都是 GC Roots,可以看出:

reference1-> 对象实例1;

reference2-> 对象实例2;

reference3-> 对象实例4;

reference3-> 对象实例4 -> 对象实例6;

可以得出对象实例1、2、4、6都具有 GC Roots 可达性,也就是存活对象,不能被 GC 回收的对象。

而对于对象实例3、5直接虽然连通,但并没有任何一个 GC Roots 与之相连,这便是 GC Roots 不可达的对象,这就是 GC 需要回收的垃圾对象。

在了解 GC 之后,开始去了解 Android 的内存泄露情况了。

Android 内存泄露场景

下面会详细介绍一些常见的内存泄露场景,以及对应的修复办法。

非静态内部类的静态实例

比如我们在 Activity 内部定义了一个内部类 InnerClass,同时定义了一个静态变量 inner,并给予赋值。假设你在 onDestory 的时候没有将 inner 置 null;那么就会引起内存泄露。原因是静态变量持有了内部类的实例,内部类会对外部类有个引用,从而导致 Activity 得不到释放。

private staticObject inner;voidcreateInnerClass() {classInnerClass {

}

inner= newInnerClass();

}

View icButton=findViewById(R.id.ic_button);

icButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

createInnerClass();

nextActivity();

}

});

记得在生命周期结束的时候,将不需要的静态变量置 null。

多线程相关的匿名内部类/非静态内部类

和非静态内部类一样,匿名内部类也会持有外部类实例的引用。多线程相关的类有 AsyncTask 类,Thread 类和 Runnable 接口的类等,它们的匿名内部类如果做耗时操作

就可能发生内存泄露,这里以 AsyncTask 的匿名内部类举例,如下所示:

voidstartAsyncTask() {new AsyncTask() {

@OverrideprotectedVoid doInBackground(Void... params) {while(true);

}

}.execute();

}super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

View aicButton=findViewById(R.id.at_button);

aicButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

startAsyncTask();

nextActivity();

}

});

当异步任务在后台执行耗时任务期间,Activity 不幸被销毁了(比如:用户退出,系统回收),这个被 AsyncTask 持有的 Activity 实例就不会被垃圾回收器回收,直到异步任务结束。

解决方法是继承 AsyncTask 新建一个静态内部类,用静态内部类创建实例就不会存在对外部实例的引用了。

Handler 内存泄露

同样道理,Handler 的 message 被传递到消息队列 MessageQueue中,在 Message消息没有被处理之前,handler 的实例也不无法被回收,如果 handler 实例不是静态的,就会导致引用它的 activity 或者 service 不能被回收,于是就会发生内存泄漏。

voidcreateHandler() {newHandler() {

@Overridepublic voidhandleMessage(Message message) {super.handleMessage(message);

}

}.sendMessageDelayed(Message.obtain(),60000);

}

View hButton=findViewById(R.id.h_button);

hButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

createHandler();

nextActivity();

}

});

对于上述问题,有两种解决办法,一种是使用一个静态的 handler 内部类,并且其持有的对象都改成弱引用形式进行引用。还有一种是在销毁 activity 的时候,将发送的消息进行移除。

myHandler.removeCallbackAndMessages(null);

这种有个问题就是 Handler 中的消息可能无法全部被处理完。

另外还有一个要注意的是,最好不要直接使用 View#post 来做一些操作。如果要用,确保要用的话,确保 view 已经被 attach 到了 window。

静态 Activity 或 View

在类中定义了静态

Activity变量,把当前运行的

Activity实例赋值于这个静态变量。

如果这个静态变量在

Activity生命周期结束后没有清空,就导致内存泄漏。因为 static 变量是贯穿这个应用的生命周期的,所以被泄漏的

Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

staticActivity activity;voidsetStaticActivity() {

activity= this;

}

View saButton=findViewById(R.id.sa_button);

saButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

setStaticActivity();

nextActivity();

}

});

为了能够被回收,需要在不需要使用的时候进行置 null 操作。比如销毁当前 activity 的时候。

特殊情况:如果一个 View 初始化耗费大量资源,而且在一个 Activity生命周期内保持不变,那可以把它变成 static,加载到视图树上 (View Hierachy),像这样,当 Activity被销毁时,应当释放资源。

staticview;voidsetStaticView() {

view=findViewById(R.id.sv_button);

}

View svButton=findViewById(R.id.sv_button);

svButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

setStaticView();

nextActivity();

}

});

同样的,为了解决内存泄露的问题,在 Activity 销毁的时候把这个 static view 置 null 即可,但是还是不建议用这个 static view的方法。

Eventbus 等注册监听造成的内存泄露

相信很多同学都在项目里面会用到 Eventbus。对于一些没有经验的同学在使用的时候经常会出现一些问题。比如说在 onCreate 的时候进行注册,却忘了反注册,或者说,在onStop的时候进行反注册,这些都会导致 Eventbus 的内存泄露。

@Overridepublic voidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);

EventBus.getDefault().register(this);//注意在onCreate()方法中注册

}

@Overridepublic voidonDestroy() {

EventBus.getDefault().unregister(this);//注意在onDestory()方法中注册

super.onDestroy();

}

注册和反注册(取消注册)是对应的,必须要添加,否则会引起组件的内存泄漏。因为注册的时候组件是被 EventBus 内部的单例队列所持有引用的。

如果你是在 View 里面注册 Eventbus 的,记得是在 View 的生命周期 onAttachedToWindow 和 onDetachedFromWindow 的时候进行注册和反注册。

最近跟我的同事进行聊天的时候发现,他们为了解决 eventbus 导致的内存泄露问题(已经成对注册和反注册还是存在内存泄露问题),于是打算创建一个 object 的实例,用这个来进行注册与反注册,这样即使发生内存泄露也只会占用很小的内存空间。

单例引起的内存泄露

项目中,经常会存在很多单例。有时候需要我们将当前 Activity 实例传给单例,然后去做一些事情。如下面的代码:

public classSingleInstance {privateContext mContext;private staticSingleInstance instance;privateSingleInstance(Context context) {this.mContext =context;

}public staticSingleInstance getInstance(Context context) {if (instance == null) {

instance= newSingleInstance(context);

}returninstance;

}

}

上述单例中传入一个 context ,就会导致 context 的生命时长和应用的生命时长一样。就会造成内存泄露。

对于这种有三种解决办法:

1、采用弱引用的方式进行引用,确保能够被回收;

2、在对应的 context 要被销毁的时候,进行置 null;确保不会长于原本的生命时长;

3、看是否能够使用 APP context;这样就不会存在内存泄露的问题了。

资源对象没关闭造成内存泄漏

当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用 Bitmap 资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。虽然有些对象,如果我们不去关闭,它自己在 finalize() 函数中会自行关闭。但是这得等到 GC 回收时才关闭,这样会导致缓存驻留一段时间。如果我们频繁的打开资源,内存泄漏带来的影响就比较明显了。

解决办法:及时关闭资源

WebView

不同的Android 版本的 webView 会有差异,加上不同的厂商定制的 ROM 的 webView 差异,这就导致 webView 存在很大的兼容性问题。weView 都会存在内存泄露问题,在应用中只要使用一次,内存就不会被释放。通常的做法是为 webView 单独开一个进程,使用 AIDL 与应用的主进程进程通信。webView 进程可以根据业务的需求,在合适的时机进行销毁。

参考文献:

1、《Android进阶解密》

java 匿名内部类内存泄露_Android 常见内存泄露 解决方案相关推荐

  1. SQL SERVER 内存分配及常见内存问题(1)——简介

    原文:SQL SERVER 内存分配及常见内存问题(1)--简介 一.问题: 1.SQL Server 所占用内存数量从启动以后就不断地增加: 首先,作为成熟的产品,内存溢出的机会微乎其微.对此要了解 ...

  2. 内存检测_Android native内存检测工具介绍

    点击上方蓝字关注我们噢~ 检测工具不仅可以在验证时发现安全问题,也可以在运用场景中阻断安全问题的发生,对于安全问题检测和攻击拦截非常友好,当然安全检测功能会消耗一定的系统性能.本文将对已集成的部分检测 ...

  3. RK3399平台开发系列讲解(内存篇)常见内存性能问题梳理

  4. Android leak内存,GitHub - jin870132/memoryleakdemo: 安卓内存泄露几种常见形式及解决方案...

    安卓内存泄露几种常见形式及解决方案 一.前言 1.内存溢出与内存泄露 内存溢出(oom),是指程序在申请内存时,没有足够的内存空间供其使用,出现oom:比如申请了一个integer,但给它存了long ...

  5. java中为什么还要防止内存泄露_JAVA防止内存的泄漏什么意思,内存还能泄露?...

    展开全部 尽管java虚拟机和62616964757a686964616fe59b9ee7ad9431333166353066垃圾回收机制管理着大部分的内存事务,但是在java软件中还是可能存在内存泄 ...

  6. Android 内存优化——常见内存泄露及优化方案

    如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回 收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄 露. 在 Android 开 ...

  7. java 多线程 内存泄露_关于内存泄露的总结

    大致先分为五个小模块: 1.什么是内存泄漏 2.有哪些情况会导致内存泄漏切如何解决 3.如何检测内存泄漏 4.Java得基本数据类型和占用字节 5.什么是内存溢出和解决办法 一.什么是内存泄漏(Mem ...

  8. 离开当前屏幕的判断方法_Android App内存泄露测试方法总结

    喜欢我的文章,欢迎关注微信公众号「软件测试艺术」,一起学习提高. 1. 内存泄露 Android系统为每一个运行的程序都指定了一个最大运行内存,超过这个值则会触发OOM机制,反应在界面就是闪退. Cr ...

  9. java 内存泄露 书籍_[Java教程]一次艰难的内存泄露排查,BeanUtils 的锅

    [Java教程]一次艰难的内存泄露排查,BeanUtils 的锅 0 2020-10-29 18:24:42 现象 通过jstat -gcutil pid 5000 ,发现fgc次数很多而且频繁,此时 ...

最新文章

  1. WPF 分批加载十万个按钮
  2. PaddlePaddle训练营——公开课——AI核心技术掌握——第2章机器能“看”的现代技术——源自视觉神经原理的卷积网络简介及深入理解
  3. 【Linux C 多线程编程】互斥锁与条件变量
  4. SMO算法原理转载+自己补充
  5. CentOS利用crontab执行计划任务
  6. Java从入门到精通——数据库篇Mongo DB GridFS文件系统
  7. 使用MSYS2编译64位gvim
  8. linux里强制覆盖,Linux cp命令无法强制覆盖
  9. php 如何将xml转为数组array
  10. vs2019键盘钩子_注册全局鼠标钩子后用鼠标点击窗口上最大化、最小化、关闭窗口界面卡死...
  11. 硅谷真假u盘测试软件,硅谷硅谷真假u盘测试
  12. 联想电脑预装office自动卸载工具
  13. EXCEL表格中数字金额很大时后面零很多,如何设置直接以万元为单位显示,不显示后面的零
  14. 金鳞岂是池中物IT评论博客正式成立
  15. 第十一周项目2--定义点类
  16. 强势Mac机网站设计软件:10大最佳HTML编辑器
  17. PHP赛事贝格尔编排法--单循环
  18. # cs231n (四)反向传播
  19. 什么是“Ground truth”
  20. 联想G455 XP/MAC 双系统安装

热门文章

  1. 上面两点下面一个三角形_三角形的五心是三线共点的产物,聊一聊初中数学共点线的证明思路...
  2. 如何才能成为优秀设计师
  3. 在下列HTML中那也可以产生超链接,html+css+js完整版面试题(选择,简答,程序题)
  4. SQL Server 配置管理器无法打开
  5. 桌面日历和手机同步步骤(desktopcal)
  6. SP—FLASH—TOOL 报错的问题
  7. cif和cip的区别_什么是CIP贸易条款?CIP与CIF有什么区别?
  8. linux c 内存泄漏,c – linux内核中潜在的内存泄漏?
  9. win7解决IE下载excel时直接在IE浏览器中打开
  10. matrix67 kmp算法讲解