今天这篇文章主要介绍与内存相关的知识点,以及那些会导致OOM死机的因素和相应的解决方法,所以通过这篇文章你会学到以下几点:
什么是虚拟内存和物理内存。
32位和64位设备有多少虚拟内存可用。
为什么虚拟内存不足主要发生在32位设备上。
如何解决虚拟内存不足的问题
App启动后虚拟内存的分配。
如何解决Java堆内存不足的问题
Java堆上仍然有大量的可用内存为什么OOM还在
做性能优化应该关心哪些指标数据。
不知道朋友们有没有经历过同样的优化方案应用A上线后,崩溃率下降很多,但应用B上线只有一点利润每个优化方案对不同app的优化效果可能不一样,因为每个app在不同国家和地区面对的用户群体不一样,所以模型也不一样所以我们需要了解记忆相关的知识点,结合线上线下的数据,对自己的app进行属性化,对症下药
记忆是极其稀缺的资源不合理的使用会导致可用内存越来越少,可能导致停滞,ANR,OOM崩溃,原生崩溃等,并严重影响用户体验所以我们在做性能优化的时候,内存优化是非常重要的一个环节
在内存优化的前期,我们都有一种内存占用越少越好的潜意识,这在某些情况下是错误的比如在高端电脑上,我们可以多分配一些内存,可以提高用户体验,但是在低端电脑上,内存本身就很小,所以要尽量减少内存的分配比如动画,特效等针对性能损失可以在低端电脑上关闭,或者关闭硬件加速,采用其他方案代替,既能减少死机,又能减少停滞,提高用户体验
因为Java有自动回收机制,所以在开发过程中,很少有人会在意内存问题,脑子里会有一个潜意识的GC会自动回收因此,位图,动画,播放器等无用资源用完不会主动释放,等待GC回收在实际项目中,依靠GC是不可靠的首先,GC的自动恢复机制是不确定的,GC也分为不同的类型如果发生全GC,会触发stop the world事件,会让App更严重
当可用内存越来越少时,严重时会导致OOM崩溃做过OOM优化的朋友应该会发现,网上抓取的大部分OOM崩溃栈都是压死骆驼的最后一根稻草,并不是问题的根源因此,我们需要对OOM崩溃进行归因,并找到内存的大部分减少整机使用的内存,从而减少OOM死机,所以我大致分为以下几个方面
虚拟内存和物理内存
堆内存
堆内存泄漏是指程序运行时分配给对象的内存当程序退出或退出接口时,所分配的内存没有被释放或由于其他原因不能被释放
资源泄漏,如FD,socket,thread等,在每部手机上数量有限如果你用了又不释放,就会因为资源耗尽而崩溃我们在网上看到过FD泄露,导致三倍的崩溃率
分配的内存达到Java堆的上限。
有很多可用的内存,因为内存是碎片,没有足够的空间分配给连续的段。
对象的累积单次分配或多次分配过大,例如位图总是在循环动画中创建。
Java堆内存溢出
内存泄漏
FD的数量超过了当前手机的阈值
线程数量超过了当前手机的阈值。
其中FD和线程崩溃所占比例较低,所以这不是我们前期优化的重点本文重点介绍虚拟内存和物理内存,下一篇文章将介绍堆内存堆内存是程序在运行过程中为对象分配内存的区域,也属于虚拟内存的范围
虚拟内存和物理内存
在介绍虚拟内存之前,我们需要介绍一下物理内存,也就是实内存。如果应用程序直接在物理内存上操作,会有很多问题:
安全问题,应用之间的内存空间没有隔离,会导致应用A修改应用B的内存数据,非常不安全。
内存利用率低,应用程序使用内存时会出现内存碎片即使仍有大量可用内存,但在连续的段中没有足够的内存分配,这将导致崩溃
效率低当多个应用同时读写物理内存时,使用效率会很低
为了解决上述问题,我们需要为每个应用程序分配中间内存,这些内存最终会映射到物理内存,接下来称为虚拟内存。
操作系统会为每个应用分配一个独立的虚拟内存,实现应用之间的内存隔离,避免应用A修改应用b内存数据的问题,虚拟内存最终会映射到物理内存当应用程序申请内存时,它将获得虚拟内存,只有在实际执行写操作时,虚拟内存才会分配给物理内存优点是应用程序可以使用连续的地址空间来访问不连续的物理内存
每个应用程序可以使用的虚拟内存大小受到CPU位宽和内核的限制我们常说的16位cpu,32位cpu,64位CPU都是指CPU的位宽,也就是指一次可以处理的数据宽度,也就是CPU可以处理的二进制位数,分别是16位,32位和64位目前市场上常用的是32位和64位设备
32位设备和64位设备的可用虚拟内存分别是多少32位设备的虚拟内存大小为3GB
32位CPU架构设备的可用地址空间为2 32 = 4GB,虚拟内存空间分为内核空间和用户空间系统提供了三个虚拟地址空间分配参数,它们代表用户空间可访问的虚拟地址空间
VMSPLIT_3G:默认值,表示用户空间可以使用3GB的低位地址,剩余1GB的高位地址分配给内核。
VMST _ 2G:表示用户空间可以使用2GB的低位地址。
VMST _ 1G:表示用户空间可以使用1GB的低位地址。
64位应用程序可以使用的虚拟内存大小是512GB。
64位CPU架构设备虽然有64位地址空间,但并不是都能用对于以后的扩展,只能使用部分地址
Android默认虚拟地址的长度配置为CONFIG_ARM64_VA_BITS=39,即Android 64位应用的可用地址空间为2 39 = 512 GB。
当32位应用程序在64位设备上运行时,它可以使用4GB的虚拟地址空间,而64位应用程序可以使用512GB的空间所以64位机器上不存在虚拟空间不足的问题所以2019年Google Play既要64位版本,也要32位版本
在我们的OOM崩溃设备中,32位设备占50%+以上,虚拟内存不足主要发生在32位设备上。
为什么虚拟内存不足主要发生在32位设备上。
在32位设备上,受限于最大内存4 GB的地址空间,内核空间占用1G,剩下的3G是用户空间我们可以通过解析/process/ pid/smaps文件来检查当前的虚拟内存分配
系统资源预分配,包括Zygote进程初始化时需要加载的框架层的代码和资源Fork的子流程可以直接使用框架资源包括:框架层的Java代码,so,art虚拟机,各种静态资源字体,文件等等
在系统的预分配区域中,有一个区域占用了130MB的内存。
app自身的资源,包括App中的代码和资源,App直接或间接启动线程消耗的堆栈空间,App应用的内存,内存文件映射等。
Java堆用于分配Java/Kotlin创建的对象它由GC管理和回收当GC回收时,From空间中的对象被复制到to空间这两个区域分别是达尔维克—主空间和达尔维克—主空间1这两个区域的大小和我目前测试的Java堆大小一样,都是512 MB,如下图所示
RAM—dal vik—heap . MK heap phone—hdpi—dal vik—heap . MK 32512—dal vik—heap . MK 1281024—dal vik—heap . MK 2562048—dal vik—heap . MK 5124096—dal vik—heap
内存映射,mmap是一种内存映射文件的方法我们的APK,德克斯,索等等都是通过mmap读取的,会导致虚拟内存的增加mmap占用的内存与读写有关
在分析了内核,系统资源,各App的资源占用情况后,最后留给我们使用的内存已经不多了,要合理利用系统资源,真正做到分配时间,及时释放。
如何解决虚拟内存不足的问题
目前业内有很多黑科技来释放系统占用虚拟内存不足的问题大概有以下优化
本机线程的默认堆栈空间约为1M经过测试,线程中执行的逻辑大多数情况下不需要这么大的空间,所以将原生线程的堆栈空间减半可以减少pthread_create OOM的崩溃
在系统预分配的区域中,有一个区域占用了130MB内存,可以尝试释放WebView的预分配内存,减少一些虚拟内存。
虚拟机堆空间减半如上所述,有两个大小相同的区域,即dalvik—main空间和dalvik—main空间1把虚拟机堆空间减半,其实就是减少其中一个主空间占用的内存
为了优化Aauto更快的垃圾收集器jemalloc,释放了anon:libc_malloc占用的虚拟内存。
以下统计显示,Android7.0App第一次启动时libc_malloc占用的虚拟内存为156 mbvsspssrssname 159744 kb 81789 kb 82320 kb。
Android 11之前使用的垃圾收集器是jemalloc,Android 11之后默认使用的垃圾收集器是scudo。
App启动后虚拟内存的分配。
下图是App在Android 7.0上启动后占用的虚拟内存不同系统和app的虚拟内存分配是不一样的我们可以通过解析/process/ pid/smaps文件来检查应用程序的虚拟内存分配
如上图所示,主要分为三个部分:
Dalvik,程序运行时为对象分配内存的区域。
程序文件索引,所以,oat
当地的
针对以上问题,我们在项目中优化了以下方法,重点优化dalvik占用的内存。由于篇幅问题,我们将在以下文章中进行详细分析:
从Android 3.0到Android 7.0,位图对象和像素数据主要放在Java堆中,Java堆上限为512MB,而Native占用虚拟内存,32位设备可以使用3GB,而64位设备更大,可以尝试将位图分配给Native,缓解Java堆的压力,减少OOM崩溃。
收敛位图,避免重复创建位图,及时退出界面释放资源。
内存回收策略:当活动或片段泄露时,动画,位图,DrawingCache,背景,监听器等与它关联的不能被释放当我们退出界面时,我们递归遍历所有子视图,释放相关资源,减少内存泄漏占用的内存
收敛线程,祖先代码已经使用了new Thread,AsyncTask,自创线程池等在项目的很多地方通过统一线程池的方式,减少App创建线程数量,降低系统成本
对低端电脑和高端电脑采取不同的策略,减少低端电脑的内存占用。
内存泄漏永远无法解决,所以需要对排名靠前的系列泄漏进行梳理,重点是占用内存最多的泄漏和使用最频繁的场景产生的泄漏。
创建了大量小对象,累积的堆内存太大一般有明显的叠加,可以根据叠加信息求解例如,位图总是在循环动画中创建
大对象,堆的单个分配内存太大。
删除代码以减少dex文件占用的内存
减少App中的dex数量,不必要的功能可以动态分配。
按需加载so文件,不要提前加载所有so文件,需要的时候再加载。
Java堆上仍然有大量的可用内存为什么OOM还在
很多朋友都问过我这样一个问题,大概归结于以下几个原因:
内存碎片,没有足够的内存分配给连续的段。
虚拟内存不足
或者线程数量FD超过当前手机的阈值。
在文章的最后,我想提一下,我们在做性能优化的时候,不仅要关心性能指标数据,更要关心对业务指标数据的影响,比如能提高多少使用时长,留存等等。
为什么需要关心业务指标数据。
性能数据,如OOM崩溃率,本机崩溃率,ANR等,可能只有客户端的小伙伴知道,其他的他们不知道也不会关心这些,但是他们对使用时间,留存等业务指标更敏感,更能体现做这件事的价值这只是我自己的观点每个人都有不同的视角和观点
这是全文的结尾。本文只是梳理了与记忆相关的知识点,哪些因素会导致
崩溃和相应的解决方案下一篇文章将介绍堆内存,这是程序在运行过程中为对象分配内存的区域
郑重声明:此文内容为本网站转载企业宣传资讯,目的在于传播更多信息,与本站立场无关。仅供读者参考,并请自行核实相关内容。
|