美文网首页
Android的垃圾回收与内存泄露

Android的垃圾回收与内存泄露

作者: 背影杀手不太冷 | 来源:发表于2016-06-24 10:28 被阅读400次

标签(空格分隔): Android


我们知道App都有一个UI线程,也叫主线程,那是Android框架帮我们创建的,这里注意的是不是每个activity对应一个UI主线程,而是一个App。
为Message都在一个队列中,拥有它下一个对象的引用非常重要,这里的写法其实跟我们上学时学习的队列类似,每一个实体类都拥有下一个的引用,这样就构成了一个队列

内存泄漏的基本知识请见博客一
如何高效使用handler避免内存泄漏请见博客二

Looper造成内存泄漏的总结为:
1、因为Looper里面的MesageQueue持有外部传进来的runnable引用,runnable又持有handler的引用,handler持有activity的引用//这种情况在用handler开启一个耗时的后台操作时,这时开启的线程也会持有handler的引用,在关闭`Activity`的时候停掉你的后台线程。线程停掉了,就相当于切断了`Handler`和外部连接的线,`Activity`自然会在合适的时候被回收。
2、在使用handler.handleMessage()这个方法时,实际上是一个回调的过程
,在message的内部handleMessage()将肯定是持有Handler的引用,而handler又持有activity的引用

会有一条链`MessageQueue -> Message -> Handler -> Activity`,由于它的引用导致你的`Activity`被持有引用而无法被回收`

weakHandler博客
WeakHandler的实现原理:
WeakHandler的思想是将Handler和Runnable做一次封装,我们使用的是封装后的WeakHandler,但其实真正起到handler作用的是封装的内部,而封装的内部对handler和runnable都是用的弱引用。

自己的理解:###

因为内部封装了的handler与runnable都是弱引用,所以实际上供调用者使用的是封装之后的weakHandler与weakRunnable,所虽然在Looper持有的weakhandler与weakRunnable,而weakHandler与weakRunnable又分别持有弱引用的handler与弱引用的runnable,所以当被封装了的handler与runnabl被回收时,weakHandler内部的弱引用handler与弱引用的runnable也会被回收,这时Looper持有的weakHandler内部已经没有了handler与runnable,只是一个空客,所以不会内存泄露。


以下的内容参考博客
内存泄漏潜在危害非常大,比如无意泄漏了一个Drawable,它可能只有几百K的占用,但是由于它一般会引用View,就意味着同时泄漏了View,Context,Activity 以及 Activity中的resource,这个内存的泄漏就非常可观了。
安卓中很容易出现这种连锁的引用泄露

造成内存泄露的情况有下面两种:##

1、try/catch/finally中网络文件等流的没有手动关闭###

  • HTTP
  • File
  • ContendProvider
  • Bitmap
  • Uri
  • Socket

2、onDestroy() 或者 onPause()中未及时关闭对象###

  • 线程泄漏:当你执行耗时任务,在onDestroy()的时候考虑调用Thread.close(),如果对线程的控制不够强的话,可以使用RxJava自动建立线程池进行控制,并在生命周期结束时取消订阅;
  • Handler泄露:当退出activity时,要注意所在Handler消息队列中的Message是否全部处理完成,可以考虑removeCallbacksAndMessages(null)手动关闭
  • 广播泄露:手动注册广播时,记住退出的时候要unregisterReceiver() 第三方SDK/开源框架泄露:ShareSDK,
  • JPush等第三方SDK需要按照文档控制生命周期,它们有时候要求你继承它们丑陋的activity,其实也为了帮你控制生命周期
  • 各种callBack/Listener的泄露,要及时设置为Null,特别是static的callback
  • EventBus等观察者模式的框架需要手动解除注册
  • 某些Service也要及时关闭,比如图片上传,当上传成功后,要stopself()
  • Webview需要手动调用WebView.onPause()以及WebView.destory()

static class/method/variable 的区别,你真的懂了吗?##

(1). Static inner class 与 non static inner class 的区别
static inner class即静态内部类,它只会出现在类的内部,在某个类中写一个静态内部类其实同你在IDE里新建一个.java 文件是完全一样的。


此处输入图片的描述此处输入图片的描述

可以看到,在生命周期中,埋下了内存泄漏的隐患,如果它的生命周期比activity更长,那么可能会发生泄露,更可怕的是,有可能会产生难以预防的空指针问题。这个泄露的例子,详见内存管理(2)的文章

(2). static inner method
静态内部方法,也就是虚函数:可以被直接调用,而不用去依赖它所在的类,比如你需要随机数,只用调用Math.random()即可,而不用实例化Math这个对象。在工具类(Utils)中,建议用static修饰方法。static方法的调用不会泄露内存

(3). static inner variable
慎重使用静态变量,静态变量是被分配给当前的Class的,由类的所有实例共享,而不是一个独立的实例,当ClassLoader停止加载这个Class时,它才会回收。在Android中,需要手动置空才会卸掉ClassLoader,才能出现GC。

当你旋转屏幕后,Drawable就会泄露。

匿名内部类实际上就是non-static inner class,所以也会有non-static inner class的缺点##

单例模式(Singleton)是不是内存泄漏?##

在单例模式中,只有一个对象被产生,看起来一直占用了内存,但是这个不意味就是浪费了内存,内存本来就是用来装东西的,只要这个对象一直被高效的利用就不能叫做泄露。但是也不要偷懒,一个劲的全整成了单例,越多的单例会让内存占用过多,放在Application中初始化的内容也越多,意味着APP打开白屏的时间会更久,而且软件维护起来也变得复杂。

好的例子:GlobalContext,SmsReceiver动态注册,EventBus

为什么大神喜欢用static final来修饰常数?##

static由于是所有实例共享的,说到共享一定要加锁,万一某个实例更改它后,其它的实例也会受到影响,所以加入final作为永久只读锁以防止常数被修改。

下面的话什么意思,看不懂???

全局变量生命周期是classloader,有坑。你的activity在finish后变量并不会改变。这个在面试中经常遇到,问你经过多次计算后,static的值是多少。比如在Android中有个坑,最常见的就是把一个sharedpreference赋值给一个static变量,然后又把sharedpreference改变后,再次调用这个static变量,就发现变量并没有改变,这个在debug中很难发现。

顺便说下final吧##

final 变量:是只读的;
final 方法:是不能继承或者重写的。
final 引用:引用不能修改,但是对象本身的属性可以修改;
final class:不可继承;
final不会让代码速度更快

Bitmap的使用##

使用前注意配置Bitmap的Config,比如长宽,参数(565, 8888),格式;
使用中注意缓存;
使用后注意recycle以清理native层的内存。
2.3以后的bitmap不需要手动recycle了,内存已经在java层了。同时,Bitmap还有别人做好的轮子,比如PhotoView,Picasso,就可以方便的解决OOM问题。

线程泄露可能是最严重的泄露问题##

例如在activity中开了一个线程去上传图片,完成之后弹出toast,但是在还没有上传完成之前,点击了推出了activity,注意上传线程是还在跑,当上传完成之后,却发现window没了,toast弹不出所以抛出异常
所以应该在activity退出的时候把上传线程也要停止了。

Context与ApplicationContext##

Context的生命周期是一个Activiy,而ApplicationContext的生命周期是整个程序。我们最要注意的就是Context的内存泄露。
在Activiy的UI中要使用Context,而在其他的地方比如数据库、网络、系统服务的需要频繁调用Context的情况时,要使用ApplicationContext,以防止内存泄露。
为什么ApplicationContext不会内存泄漏???


其他的小技巧##

1、Listview的item泄露###

这个是入门问题了,加入ViewHolder可以减少findViewById的时间,或者使用RecyclerView,来解决“滑动很卡”的问题。这个实质也是一个单例。

2、StringBuilder###

首先说一下String,StringBuilder,StringBuffer的区别:

  • 字符串是不可变的,因为字符串都是常量!如果你试着改变它们的值,另一个对象被创建,而StringBuffer和StringBuilder是可变的,这样他们就可以改变它们的值
  • StringBuffer是线程安全的。当应用程序只需要运行在单个线程则最好使用StringBuilder。StringBuilder比StringBuffer更有效率,因为StringBuffer其实是给StringBuilder加了同步锁;其实你使用Log.d(TAG,"xx"+"yy")这类写法后,编译器生成的代码已经自动帮你变成StringBuilder了

StringBuffer原理分析:###

将字符串拼接时(不管是字面常量也好,或者是变量,方法调用的结果也好),即用“+”将多个字符串拼接时,实际上都是变成StringBuilder。如果一个字符串(不管是字面常量也好,或者是变量,方法调用的结果也好)
new StringBuilder().append( string_exp ).append( any_exp ).toString()
如果表达式里有多个+号的话,后面相应也会多多几个StringBuilder.append的调用,最后才是toString方法。
   StringBuilder(String)这个构造方法会分配一块16个字符的内存缓冲区。因此,如果后面拼接的字符不超过16的话,StringBuilder不需要再重新分配内存,不过如果超过16个字符的话StringBuilder会扩充自己的缓冲区。最后调用toString方法的时候,会拷贝StringBuilder里面的缓冲区,新生成一个String对象返回。
  所以在在我们经常将一些基本数据类型转化成字符串时,例如经常是这样做的:String text=100+"";虽然可以将整数100转化成“100”字符串,但是一个StringBuilder对象,一个char[16]数组,一个String对象,一个能把输入值存进去的char[]数组。这样是很浪费内存的,所以推荐使用String.valueOf,即String text=String.valueOf(100);这样至少StringBuilder对象省掉了。
  有的时候或许你根本就不需要转化基础类型。比如,你正在解析一个字符串,它是用单引号分隔开的。最初你可能是这么写的:
  final int nextComma = str.indexOf("'");
  或者是这样
   final int nextComma = str.indexOf('\'');


  • 同时,使用字符串进行逻辑运算是相当缓慢的,,不建议,因为JVM将字符串转换为字节码的StringBuffer。浪费大量的开销将从字符串转换为StringBuffer然后再返回字符串

综上所述:尽量使用StringBuilder,而不用String来累加字符串

多用基本类型##

使用int而不用Integer,较少的对象花销。在Android中使用sparseArrayMap取代HashMap就是把key变成了int,而一定程度上减小了内存占用。

Native代码不受GC控制##

使用弱引用##

使用弱引用可以防止一定程度的无意引用造成的泄露,比如在Handler中使用弱引用作为参数,当销毁的时候就有可能不会发生泄露。
但是弱引用随时可能为null,使用前需要判断是否为空

相关文章

  • java垃圾回收算法

    垃圾回收机制的意义 垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存;内存泄露:指该内存空间使用完毕后未回收,...

  • Android的垃圾回收与内存泄露

    标签(空格分隔): Android 内存泄漏的基本知识请见博客一如何高效使用handler避免内存泄漏请见博客二 ...

  • Android性能调优(4) — 内存泄漏与内存抖动

    在上一遍《Android性能调优(3)—内存管理与垃圾回收》我们对Android内存管理与垃圾回收有了一定的认识。...

  • Java内存泄漏

    本文将会介绍: C++中的内存泄露 Java内存管理与垃圾回收 Java中的内存泄漏 一、C++中的内存泄露 在大...

  • Java 垃圾回收机制整理

    垃圾回收的意义 如果不进行垃圾回收,内存迟早会被消耗空。垃圾回收机制的引入可以有效的防止内存泄露、保证内存的有效使...

  • Android内存泄漏

    内存泄露就是指该被GC垃圾回收的,由于有另外一个对象仍然在引用它,导致无法回收,造成内存泄露,过多的内存泄露会导致...

  • 记一次面试

    内存泄漏和内存溢出 概念 内存泄漏:垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。 内存溢出:...

  • Android 内存泄露:详解 Handler 内存泄露的原因

    前言 在Android开发中,内存泄露 十分常见 内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中内存泄...

  • Android 对象池实现原理和简单使用

    了解Android 垃圾回收 判断对象是否可以被回收垃圾收集算法内存分配与回收策略 GC频繁原因 Memory C...

  • python--垃圾回收机制,使用gc、objgraph干掉py

    目录 一分钟版本 python内存管理引用计数垃圾回收gc module 内存泄露objgraph查找内存泄露 循...

网友评论

      本文标题:Android的垃圾回收与内存泄露

      本文链接:https://www.haomeiwen.com/subject/twyodttx.html