Android内存优化实践
1.内存模型与分布
我们知道android应用大多是使用java语言进行开发的,这就需要我们了解java的内存模型,此外在android中的应用都是基于Dalvik 虚拟机或者ART虚拟机,那么对这些虚拟机的内存分布也应该有所了解。
上图是常见的java虚拟机的内存分布图:
方法区:主要存储虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据。内存优化时这一部分主要考虑是不是加载了很多不必要的第三方库。这部分的内存减少主要是常量池的回收和类的卸载(类卸载条件:无引用,类加载器可卸载)
堆:几乎所有的对象都在这个区域产生,该区域属于线程共享的区域,所以写代码时更要注意多线程安全。这个内存区域的大小变化主要是对象的创建和回收,比如:如果短时间内有大量的对象创建和回收,可能会造成内存抖动,如果对象创建之后一直回收不掉,则会导致内存泄漏,严重的内存泄漏会导致频繁的gc,从而是界面卡顿。
虚拟机栈:这个区域描述的是java方法执行的内存模型,我们常说的方法栈的入栈就是将方法的栈帧存储到虚拟机栈,这个区域是线程私有的,其生命周期就是线程的生命周期。也就是说每个线程都会有,默认一个线程的线程栈大小是1M,这不包括在方法中产生的其他对象的大小。这一块我们能控制的就是线程的数量,特别是程序中没有使用线程池或者使用的多个第三方库都带有线程池的情况。
本地方法栈:同虚拟机栈的作用非常类似,是为虚拟机执行native方法服务的,所以需要注意的地方也和虚拟机栈一样,特别是使用了第三方so的情况
程序计数器:当前线程执行的虚拟机字节码的行号记录器,占用的内存较小,可以不考虑
2.内存限制
android是基于Linux系统的,android中的进程分为两种:
1.native进程:采用C/C++实现,不包含dalvik实例的linux进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的
2.java进程:实例化了dalvik虚拟机实例的linux进程,进程的入口main函数为java函数。dalvik虚拟机实例的宿主进程是fork()系统调用创建的linux进程,所以每一个android上的java进程实际上就是一个linux进程,只是进程中多了一个dalvik虚拟机实例
我们知道,操作系统对进程的内存是有限制的,而且操作系统对dalvik虚拟机自身的堆内存大小也是有限制的。可以通过如下命令查看限制大小:
adb shell getprop | grep dalvik.vm.heapgrowthlimit
可以在Androidmanifest文件中application节点加入android:largeHeap=“true”来增加其dalvik虚拟机中堆的大小
我们常说的堆大小其实是包涵两部分的,一是java的堆,而是native的堆,java堆中主要是一下java对象,由 C/C++申请的内存空间则在native堆中,也有一些对象需要结合native和java堆共同完成,比如bitmap,bitmap分为bitmap对象和其中存储的像素值,对象分配在java堆,而存储的像素值则根据版本不同存储的位置也不同,api 11 - api 25是存储在java堆中的,其他版本是存储在native堆中的
3.内存泄漏
常见的内存泄漏:
1.静态引用(自身代码和第三方代码)
2.集合内引用
3.Handler消息未清除
4.非静态的内部类中持有外部内的应用
5.匿名内部类/非静态内部类和异步线程
检查的方式:
我这里使用的是leakcanary,一般简单的内存泄漏可以直接在leakcanary中查到引用链路,不能查看的我是使用MAT来分析的
上图中各项详细的指标的意义可以在这里查到,这里主要占比比较大的几个区域:
allocated:表示app内分配的java的对象数,从当前数值可以看出程序内可能存在过多创建对象的情况,比如string对象
Native:从 C 或 C++ 代码分配的对象内存,频繁进出相关页面发现native堆的大小并没有减小,说明存在c/c++层的内存泄漏
Code:您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。这个区域能优化的就是移除不需要的so库,懒加载使用so库,移除无用代码(import,方法和类)
4.优化实践
了解了android中的内存分布和泄漏相关,接下来就是结合自身业务进行内存优化了,如下:
1.先解决程序中内存占用较大的业务模块中的内存泄漏,不熟悉MAT的使用的可以看看这个
2.移除程序中多余的代码和引用,这里使用默认的lint检测再配合shrinkResources来删除无效资源
3.优化图片,保证图片放置在合理的文件夹,根据View大小加载合适的图片大小,根据手机状态配置bitmap和回收策略
4.优化对象创建,比如string,使用对象池等
参考文章:
Android内存管理(http://developer.android.com/intl/zh-cn/training/articles/memory.html)
android中bitmap内存优化(https://www.jianshu.com/p/3f6f6e4f1c88)
jvm和Dalvik虚拟机的区别(https://www.cnblogs.com/lao-liang/p/5111399.html)
https://blog.csdn.net/lithe/article/details/48656345
内存泄漏(https://sdk.cn/news/2013)
- HDU 1874 畅通工程续【Floyd算法实现】
- 接口测试 | 21 基于flask弄个restful API服务出来
- 数论部分第二节:埃拉托斯特尼筛法 埃拉托斯特尼筛法
- [接口测试 -基础篇] 20 用flask写一个简单server用于接口测试
- 接口测试 | urllib篇 19 urllib基本示例
- 接口测试 | urllib篇 18 urllib介绍
- 【专知-Deeplearning4j深度学习教程01】分布式Java开源深度学习框架DL4j安装使用: 图文+代码
- .Net Core Runtime安装说明
- 【专知-Deeplearning4j深度学习教程02】用ND4J自己动手实现RBM: 图文+代码
- 【专知-Deeplearning4j深度学习教程03】使用多层神经网络分类MNIST数据集:图文+代码
- TypeScript 1.6发布:完全支持React/JSX
- 【专知-Java Deeplearning4j深度学习教程04】使用CNN进行文本分类:图文+代码
- sql server之数据库语句优化
- 【专知-Java Deeplearning4j深度学习教程05】无监督特征提取神器—AutoEncoder:图文+代码
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 算法篇:位运算进阶(二)
- 算法篇:摩尔投票法的使用
- 理解ECMAScript规范(2)
- 算法篇:数的转换
- React 开发要知道的 34 个技巧
- 算法篇:求1的个数
- 新特性解读 | MySQL 8.0 语句摘要功能介绍
- 10个不那么知名但很实用的Web API
- 技术分享 | 一文了解高并发限流算法
- prometheus-operator 监控 k8s 外部集群
- Kubernetes 通过statefulset部署redis cluster集群
- 猿实战13——实现你没听说过的前台类目
- 猿实战14——前台类目之广告牌设置
- 猿实战15——关联你所不明白的前后台类目
- 完美解决方案-雪花算法ID到前端之后精度丢失问题