Android性能优化 (1)—— 内存溢出和内存泄漏的介绍

项目中的内存优化对于每一个程序员来说都是必须掌握的。大家对于app中的卡顿现象有过系统的了解过吗?

接下来我们通过学习下面的文章,来对内存卡顿现象有一个深刻的了解。希望能帮助到大家。

什么是卡顿

我们可以先说说流畅性:
流畅我们定义为运行程序时达到60fps或以上的绘制效率,且尽可能少丢帧。
流畅和卡顿是建立在不同的标准上。如果强加议论,一定会变成毫无意义的口水战。在这里,卡顿我们定义为程序运行时无法达到60fps,丢帧频繁。

比如说下面的栗子:

堆内存溢出

运行上面代码,不到一分钟将导致手机卡死。我的手机数华为荣耀plus,一会时间手机就卡死。

内存泄露:

指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

简单的理解为: 内存没有回收

例如:
没有调用 bitmap.recycle()

内存溢出:

指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

简单的理解为: 内存不够使用

例如:申请了一个integer,只有存放integer的空间.但给它存了long才能存下的数,那就是内存溢出。原因:申请不到可用的内存

注意:一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。——>memory leak会最终会导致out of memory!

常见内存泄漏的应用现象:

Android(Java)中常见的容易引起内存泄漏的不良代码

  • 查询数据库没有关闭游标

  • 构造Adapter时,没有使用缓存的 convertView

  • Bitmap对象不再使用时调用recycle()释放内存

  • 释放对象的引用

  • 使用Adapter时,没有复用convertview

  • 使用非静态内部类

  • 单例模式时需要context参数时,

  • 注册对象时,没有进行反注册

  • 使用资源对象时,没有关闭

  • 使用Bitmap后没有用recycle释放

  • 集合中的对象没有清理

  • 频繁创建对象而没有释放

内存溢出的流程图(来自百度):

image

java内存泄露的图文介绍:

image

发生的方式来分类,内存泄漏可以分为4类:

    1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
  1. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

  2. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。

  3. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

大家做项目的时候,我想有些童鞋们不经意间会出现内存泄露的象限:

比如你在onCrate()调用了

方法或者

方法, 但是并没有在相对应的
onDestroy()
方法调用

方法或者

方法

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害。
作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。

从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到 。

几个内存泄露的案例:

1. 静态集合类 像HashMap、Vector等的使用最容易出现内存泄露

2.当集合里面的对象属性被修改后,再调用remove()方法时不起作用

3、监听器

在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

4、各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

5.内部类和外部模块等的引用

例如程序员A 负责A 模块,调用了B 模块的一个方法如:

这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用

6、单例模式

显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况

如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809
微信公众号:终端研发部

Markdown

(欢迎关注学习和交流)

代码交流 2021