在我们日常代码时候,可能会发生OOM(OutOfMemoryError)。这时候,我们通常要通过各种检测进行对内存的检测,追踪问题。在此之前,我们先了解一下Android的虚拟机与内存管理,知道大致的方向,对于我们以后的追踪与避免都能提供有效的知识储备。
1. java虚拟机体系部分介绍
2. Dalvik简介
3. 内存管理部分介绍
4. ART与Dalvik的对比
5. 常见内存优化的注意点与操作
接下来我们来进行正文:
1. java虚拟机体系部分介绍
我们知道,Java支持平台无关性、安全性和网络移动性。而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,不管下层操作系统是什么。正是得益于Java虚拟机,才能达到这样的特性。
从上面我们可以看到,运行Java应用时候,会先通过Java编译器将.Java文件转化成.class文件(与平台无关的二进制字节码),当然.class文件是用于计算机阅读的,我们可以去了解内容格式与分布,不一定需要去掌握它的内容。
很多人说Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行。Java程序的运行需要Java虚拟机、Java API和Java Class文件的配合。Java虚拟机实例负责运行一个Java程序。当启动一个Java程序时,一个虚拟机实例就诞生了。当程序结束,这个虚拟机实例也就消亡。
2. Dalvik简介
Dalvik是Google针对Android设计的一款Java虚拟机,它通过将.class转化为.dex格式,使得程序具有更佳高效。其中,dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统,使得Dalvik具有高效、简洁、节省资源的特点。
从功能上看,Dalvik作为面向Linux、为嵌入式操作系统设计的虚拟机,主要负责完成对象生命周期管理、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等。Dalvik充分利用Linux进程管理的特定,对其进行了面向对象的设计,使得可以同时运行多个进程,而传统的Java程序通常只能运行一个进程,这也是为什么Android不采用JVM的原因。Dalvik为了达到优化的目的,底层的操作大多和系统内核相关,或者直接调用内核接口。另外,Dalvik早期并没有JIT编译器,直到Android2.2才加入了对JIT的技术支持。
这里我们看看Dalvik(DVM)与JVM的区别:
1. 大多数Java虚拟机都是基于栈的结构,而Dalvik虚拟机则是基于寄存器。一般而言,执行同样的功能,前者需要更多的指令(主要是load和store指令),而后者需要更多的指令空间;
2. Java虚拟机运行的是Java字节码,而Dalvik虚拟机运行的是专有文件格式dex;
在Java程序中,Java类会被编译成一个或多个class文件,然后打包到jar文件中,接着Java虚拟机会从相应的class文件和jar文件中获取对应的字节码。Android应用虽然也使用Java语言,但是在编译成class文件后,还会通过DEX工具将所有的class文件转换成一个dex文件,Dalvik虚拟机再从中读取指令和数据。dex文件除了减少整体的文件尺寸和I/O操作次数,也提高了类的查找速度;
Dalvik虚拟机具有以下特点(还有好几个,但是个人觉得这三个比较重要或者好记):
1. 使用dex格式的字节码,不兼容Java字节码格式
2. 代码密度小,运行效率高,节省资源
3. 有内存限制
......
从dex的文件结构上看,dex文件结构和class文件结构差异的地方很多,但从携带的信息上看,dex和class文件是一致的。当然dex是可优化的,优化后一般是原来的1~4倍大小(详细见参考博客)。而一个Android应用程序,需要经过以下过程才可以在Dalvik虚拟机上运行:
1. 把Java源文件编译成class文件
2. 使用DX工具把class文件转换成dex文件
3. 使用aapt工具把dex文件、资源文件以及AndroidManifest.xml文件(二进制格式)组合成APK
4. 将APK安装到Android设备运行
3. 内存管理部分介绍
让我们看看Java运行时,将内存如何分划(具体解释有点长,就不赘述了,有兴趣可见参考博文)
对比之下,Android系统的ART和Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色, 使用paging和memory-mapping来管理内存(即使用堆Heap进行内存管理),这意味着不管是因为创建对象还是使用使用内存页面造成的任何被修改的内存,都会一直存在于内存中,App唯一释放内存的方法就是释放App持有的对象引用,使GC可以回收。
Android每个被运行的应用都会分配一个Dalvik虚拟机实例,这样在有限的内存与资源情况下,就会对每个应用的内存分配定下一个阀值,当然这个阀值会根据手机本身情况具有不同。我们也可以通过一些手段去修改这个阀值,但是一旦修改会影响系统,造成系统优化之类的出现卡顿,所以如非必要此方法不推荐。
而垃圾回收机制是内存管理的核心,Dalvik虚拟机使用常用的Mark-Sweep算法,该算法分Mark阶段(标记出活动对象,一般是当对象被使用的标记数目<1后记为可回收)、Sweep阶段(回收垃圾内存)和可选的Compact阶段(减少堆中的碎片)。垃圾收集的第一步是标记出活动对象,因为没有办法识别那些不可访问的对象,这样所有未被标记的对象就是可以回收的垃圾。当进行垃圾收集时,需要停止Dalvik虚拟机的运行(除垃圾收集外),因此垃圾收集又被称作STW(stop-the-world)。在ART虚拟机中这一块有进行优化,进行标记时候只挂起部分。
Android会给每个进程分配合适的内存大小,而且这种分配是弹性分配机制,即在使用过程中会根据使用App的情况,动态多分配内存,但是会有上限(阀值)。Android系统的宗旨是最大限度的让更多的进程存活在内存中,这样可以再次启动应用时,就不需要重新创建进程,提升用户体验。其中Android的进程分为以下几类:
1. 前台进程(foreground):前台进程指正在交互的应用,显示在当前屏幕或者是系统进程;
2. 可见进程(visible): 可见进程是一些不再前台,但用户依然可见的进程,举个例来说:widget、输入法等,都属于visible;
3. 桌面进程(home app):即launcher,保证在多任务切换之后,可以快速返回到home界面而不需重新加载launcher;
4. 次要服务(secondary server):目前正在运行的一些服务(主要服务,如拨号等);
5. 后台进程(hidden):通常我们按home键返回主界面,此时程序就驻留在后台,成为后台进程(background);
6. 内容供应节点(content provider): 没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该有较高的优先权;
7.空进程(empty):没有任何东西在内运行的进程,有些程序,比如BTE,在程序退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息。这部分进程无疑是应该最先终止的。
4. ART与Dalvik的对比
Android4.4及以前使用的都是Dalvik虚拟机,但是在Android 5.0之后就开始使用ART虚拟机,自然其ART对比Dalvik进行了不少优化,但是作为替代者不可避免也得对其兼容。
上文说过,Dalvik虚拟机在启动的时候会先将.dex文件转换成快速运行的机器码,又因为65535这个问题,导致我们在应用冷启动的时候有一个合包的过程,最后导致的一个结果就是我们的app启动慢,这就是Dalvik虚拟机的JIT特性(Just In Time)。
针对于JIT所造成的启动慢,ART有一个很好的特性AOT(ahead of time)。也就是说,我们在安装APK的时候就将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,ART虚拟机天生支持多dex,所以也不会有一个合包的过程,所以ART虚拟机会很大的提升APP冷启动速度。当然,这种优化也是具有代价的,其主要优缺点有:
ART优点:
1. 加快APP冷启动速度
2. 提升GC速度
3. 提供功能全面的Debug特性
ART缺点:
1. APP安装速度慢,因为在APK安装的时候要生成可运行.oat文件
2. APK占用空间大,因为在APK安装的时候要生成可运行.oat文件
5. 常见内存优化的注意点与操作
内存管理机制下,为了提高我们程序的健壮与优化性,官方也有相当的优化建议与注意点:
1. 使用优化的数据类型:Android为移动操作系统特意编写了一些更加高效的容器,例如SparseArray,今天要介绍的是一个新的容器,叫做ArrayMap。我们经常会使用到HashMap这个容器,它非常好用,但是却很占用内存;
再比如在有限资源的或者频繁调用的地方,使用StringBuilder的append代替String的+运算符操作;
2. 避免使用枚举类型:在某些大神实验后可知,同样使用静态static和使用枚举enum,后者所需要花费的内存更大(好像说是1.5倍);
3. 及时释放回收对象和复用对象;
4. 预防Activity泄漏,使用context时候,尽量使用Application Context 替代Activity Context,避免在Activity要结束时候还有引用在其他对象里面,造成无法结束而内存泄漏;
5. 图片问题(Bitmap):这应该是最常遇到内存泄漏的地方了,总之就是大图加载时候进行判断与图片压缩,注意图片复用,批量展示时候尽量使用小图展示;
6. 避免在频繁调用的方法中去进行对象创建,比如onDraw ;
7. 谨慎使用多进程;
8. 避免网络的频繁创建与销毁:这里补充一下,官方是不建议使用轮询方式进行实时更新数据的,因为它不仅较大地消耗电量,也较大地消耗流量;
总的来说,这篇文章有点散乱和粗浅,但是本来虚拟机和内存管理就是很庞杂和深奥的,作者知识能力有限,参考多个博客之后只能统(ban)筹(yun)这一些。在虚拟机与内存管理中,这些知识应该还是远远不够,甚至存在错误,如果你发现了,十万分地欢迎和感谢你的指正!
参考博文:
---------------------
作者:蓝槐魂 来源:CSDN 题目:Android Dalvik虚拟机和ART虚拟机对比
原文:https://blog.csdn.net/k1457047898/article/details/53471951
---------------------
作者: struggleqiang 来源:CSDN 题目:Android 内存管理机制
原文:https://blog.csdn.net/struggle1103/article/details/80555388
---------------------
作者:LeoLiang 题目:理解Android虚拟机体系结构
原文:https://www.cnblogs.com/lao-liang/p/5111399.html
---------------------
作者:柴华松 来源:CSDN 题目:Android内存管理机制详解
原文:https://blog.csdn.net/chaihuasong/article/details/8289367
---------------------
作者:Destiny_ZRJ 來源:简书 题目:Android异常和性能优化 - OOM异常
链接:https://www.jianshu.com/p/8de274903ca9
---------------------
作者:胡凯 题目:Android性能优化典范 - 第3季
链接:http://hukai.me/android-performance-patterns-season-3/