目录

一.内存相关概念

1.垃圾回收:

2.内存泄漏:

3.内存抖动:

二.Google版本内存相关的优化

三.内存优化最佳实践

1.自动装箱

2.Sparse数组集

3.ArrayMap

4.集合

5.枚举与常量

6.静态变量和常量

7.字符串

8.本地变量和全局变量

9.数组 VS 集合

10.图片压缩减少内存消耗

四.内存设计模式

1.对象池模式(构造型设计模式)

2.享元模式(结构型设计模式)

五.内存泄漏场景

1.静态字段

2.非静态内部类的静态实例

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

4.Handler内存泄漏

5.未正确使用Context

6.WebView

7.资源对象没有关闭

8.集合中对象没清理

9.Bitmap对象

10.监听器未关闭

六.调试工具

1.LogCat:

2.ActivityManager API

3.StrictMode

4.Meminfo

5.Memory Monitor

6.Allocation Tracker

7.Heap Dump

8.MAT

9.LeakCanary


本次内容如下:垃圾回收、内存泄漏、内存抖动、Google版本内存相关的优化、内存优化最佳实践、自动装箱、Sparse数组集、ArrayMap、集合迭代、枚举、常量、字符串、本地变量、数组 vs 集合、对象池模式、享元模式、内存泄漏场景、静态字段、非静态内部类的静态实例、多线程相关的匿名内部类或者非静态内部类、Handler内存泄漏、未正确使用context、WebView、资源对象未关闭、集合中对象没清理、Bitmap对象、监听器未关闭、调试工具、LogCat、ActivityManager API、StrictMode、Meminfo、Memory Monitor、Allocation Tracker、Heap Dump、MAT、LeakCanary

一.内存相关概念

1.垃圾回收:

找出不在被引用的对象,释放这些对象所占用的内存。

2.内存泄漏:

一个不再被使用的对象被一个还存活的对象引用着,导致GC无法回收此对象。

        专业术语:没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收此对象。

什么是GCROOT?

静态变量、常量、局部变量表(也就是方法里的变量)、本地变量表(C/C++方法里的变量)。方法里的变量之所以可以是因为它如果中途就被回收掉,那么后面如果要用到它不就不能执行了吗,这应该很好理解。 

3.内存抖动:

短时间内发生了多次内存分配和释放。

        产生原因:主要原因是短时间内频繁地创建对象。

        造成结果:影响到应用程序内存及ui整体性能,严重的内存抖动可能会导致应用程序卡顿。

二.Google版本内存相关的优化

Android 4.1引入,并在Android 4.2中改进。主要加入了平台图形相关的特性(主要是添加了垂直同步和双缓冲区),改善设备的响应能力。

Android 4.4中引入内存管理方面的改进,以支持低内存设备。

Android 5.0中引入设备电池的生命周期,添加了一些重要的api。

Android 7.0 添加了即时编译器,广播ConnectivityManager.CONNECTIVITY_ACTION(监听网络变化)只能动态注册Camera.ACTION_NEW_PICTURE(监听拍照添加存储设备)和Camera.ACTION_NEW_VIDEO不能触发(监听视频添加存储设备)

三.内存优化最佳实践

1.自动装箱

int 4byte     Integer  16byte

所以自动装箱带来不必要的内存分配,尽量不要自动装箱,用基本类型即可。

2.Sparse数组集

HashMap内部存储结构是使用哈希表的拉链结构(数组+链表)

SpareArray与HashMap主要不同:

①.它避免了对key的自动装箱(int转为Integer类型),节省内存消耗。

②.内部采用压缩的方式来表示稀疏数组的数据,节省内存消耗。

③.SparseArray在存储和读取数据时候,使用的是二分查找法。HashMap通过遍历Entry[]数组来得到对应的元素。所以在数据量大的情况下,SparseArray cpu计算量比HashMap大。

结论:在以下条件可以用SparseArray代替HashMap来节省内存消耗,提高性能

①.数据量不大,最好在千级别以内。

②.key的值是int类型或者long类型。

3.ArrayMap

设计出来是为了内存优化使用的,和SparseArray一样使用的二分法查找,不一样的是key值没有限制。

所以在数据量不大时可以代替HashMap。

在数据量不大的时候怎么选择SparseArray还是ArrayMap?

在确定key值类型是int或者long时用SparseArray,其他情况用ArrayMap。

4.集合

在Iterator循环、while循环和for循环中测评对比。耗时最短的是for循环

在for循环几种不同方式对比,耗时最短的是Java 5中增强的for循环

5.枚举与常量

枚举的好处:数量有限的元素、描述性的名字,增强了代码的可读性,还支持多态性。

内存方面:枚举值会转换成对象,消耗内存。

所以可以用静态常量代替。

6.静态变量和常量

静态变量及静态代码块在编译过程中需要经过<clinit>方法初始化,常量是直接存储在DEX文件中,所以常量没有额外的内存消耗。

7.字符串

字符串连接应该使用StringBuffer(线程安全)或者StringBuilder(线程不安全),因为他们是以字符数组的形式工作的。

8.本地变量和全局变量

具体情况看变量的可用性。

9.数组 VS 集合

如果数组和集合都可以使用情况下,数组比集合内存高效。

10.图片压缩减少内存消耗

一张位图的占用内存 = 图片长度(px)x 图片宽度(px)x 一个像素点所占的字节数

可以通过减小图片长度和宽度和一个像素所占的字节数来减少内存消耗。

BitmapFactory中的Option参数先通过inJustDecodeBounds获取宽高,然后和实际需要的宽高进行比例缩放,通过inSampleSize进行缩放,如果图片不需要那么精细,不需要透明度可以设置inPreferredConfig参数修改图片格式为565,这样内存也减少一半。

四.内存设计模式

1.对象池模式(构造型设计模式)

思想:避免可重用的对象被GC回收,节省创建对象时间。避免内存抖动。

实现角色:可重用对象,对象池,客户。

源码实例:AsyncTask工作线程中及RecycleView中View的复用。

2.享元模式(结构型设计模式)

定义:使用共享对象可以有效的支持大量的细粒度的对象。

源码实例:String中的常量池,Handler中的消息池。

五.内存泄漏场景

1.静态字段

如果静态字段引用着activity会导致activity不能被回收造成内存泄漏

比如:view是静态字段

view = findViewById(R.id.view);

这样引用着activity会造成内存泄漏

2.非静态内部类的静态实例

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

4.Handler内存泄漏

Handler是非静态内部类的实例,引用着Activity,当handler消息没有处理完Activity不能被回收,造成内存泄漏。

5.未正确使用Context

在单例模式中未正确使用context造成Activity被引用,导致不能被回收造成内存泄漏。

解决方式:尽量用Application中的context。

6.WebView

WebView在应用使用一次,内存可能不会被释放掉。

解决方法:将WebView单独放一个进程里面,根据业务需求可以在合适的实际进行销毁。

7.资源对象没有关闭

资源对象比如Cursor、file在使用完毕后再finally方法中进行关闭

8.集合中对象没清理

9.Bitmap对象

10.监听器未关闭

六.调试工具

1.LogCat:

可以查看内存回收的log

2.ActivityManager API

可以设置观察警报,在堆内存达到设定值时会自动堆转储,可以对结果进行分析。

在android 6.0中还可以通过广播接收器来监听是否达到上限值。

3.StrictMode

①.检查Activity泄漏:detectActivityLeaks

②.检查是否有未被关闭的可关闭对象:detectLeakedClosableObject

③.检查当Context被销毁,ServiceConnection和BroadcastReceiver是否存在内存泄漏

④.检查SQLite使用后是否关闭

⑤.检查一切可疑行为

4.Meminfo

调试工具命令:

adb shell dumpsys meminfo

作用:可以通过Meminfo查看是应用层内存泄漏还是Native层内存泄漏。

5.Memory Monitor

用法:android studio中的Monitors选项打开并连接手机

作用:

①.实时显示可用内存和分配的Java内存图表

②.实时显示垃圾回收事件

③.启动垃圾回收事件

④.快速测试应用程序的缓慢是否与过度的垃圾收集事件有关

⑤.快速测试应用程序崩溃是否与内存耗尽有关

⑥.打开Allocation Tracker

6.Allocation Tracker

主要作用:跟着内存分配

功能:

①.显示代码分配对象类型、大小、分配线程、堆栈跟踪的时间及位置。

②.通过重复的分配/释放来帮助识别内存变化

③.与HPROF Viewer结合使用,可以帮助你跟踪内存变化。

7.Heap Dump

主要作用是看不同数据类型在内存中的使用情况。

8.MAT

MAT分成两个标签页,一个是Overview,另一个是Leak Suspects(内存泄漏猜想)

Leak Suspects:给出MAT认为可能泄漏的地方,一般如果不是内存泄漏很明显,这个是看不出来的。

Overview:是一张饼状图,主要是显示内存的消耗,单击可以看见详细信息。里面含有一个action

Action:Action一栏列出4种action,常用的是Histogram和Domintor Tree。

Domintor Tree:可以看出对象之间的引用关系。对象到GC的路径

Histogram:从类的角度出发,可以看到类的实例个数,总共所占的内存

对比间隔一段时间的两个hprof文件:利用Compare Basket对比Domintor Tree或者Histogram,通过Histogram对比文件可以看出MainActivity增加了6个实例,所以是MainActivity中发生了内存泄漏。

9.LeakCanary

可以直接检测出Activity是否发生内存泄漏

原理:自动监控Activity在调用onDestroy方法后实例有没有销毁。