前言

内存泄漏(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. TensorFlow与OpenCV,读取图片,进行简单操作并显示
  2. python语言采用编译执行方式_Python程序的执行过程 解释型语言和编译型语言
  3. 将DataTable 写入XML
  4. 卫生间装修有哪些技巧?
  5. 企业可视化大屏如何搭建
  6. 已知p是一个指向类a的数据成员m的指针_C++ this指针的理解和作用
  7. 全国DEM下载教程 90米、30米、12.5米和5米等各种精度DEM数据
  8. linux mysql 客户端 服务端_MySQL客户端和服务器端工具集
  9. 程序员必备的10款工具软件!最后一款简直绝了!
  10. 基于RRT算法的路径规划
  11. macOS 卡顿或无法启动怎么办?教你重置Mac苹果SMC、NVRAM、PRAM
  12. 高跟鞋,五角星与黄金分割比
  13. js打开手机摄像头实现扫描二维码功能
  14. jqGrid双击事件,并获取双击行的各个属性值
  15. 计算机采用什么交换技术,计算机 交换技术是什么?
  16. linux python 例子,初学python案例 字典
  17. JAVA WEB项目隐藏url真实路径,url地址重写UrlRewriteFilter
  18. 个人对大数据概念的理解(知识结构)
  19. AutoCAD的COM开发时无法获取AtuoCAD实例对象解决方案
  20. java 修改pdf图片_Java添加、提取、替换和删除PDF图片

热门文章

  1. java servlet原理_Java Web 深入分析(8) Servlet工作原理解析
  2. 【图像识别】基于HSV和RGB模型水果分类matlab源码含 GUI
  3. oracle中文文档_如果你还在用Swagger(丝袜哥)生成接口文档,那就真有点老“土”了!...
  4. 运用spss modeler运用支持向量机_(科研工具合集之①)SPSS安装以及下载方式
  5. Curry identification.
  6. 了解CV和RoboMaster视觉组(四)视觉组使用的硬件
  7. SPVMN 视频监控联网调测 (NVR/IPC gbt-28181服务检测)
  8. JS--JavaScript提交表单(submit事件)、重置表单、取消默认提交表单(单击按钮、回车)
  9. 收获荣誉证书,显摆一下
  10. 【Microsoft Azure 的1024种玩法】六十二.利用Azure Private DNS 实现虚拟网络中域名的管理解析