Android 应用建立在 Java 虚拟机之上的, Google 为了保证同时多个 App 运行并及时唤醒, 就为每个虚拟机设置了最大可使用内存, 通过 adb 命令可以查看相应的几个参数,
- * [dalvik.vm.heapgrowthlimit]: [192m]
- * [dalvik.vm.heapmaxfree]: [8m]
- * [dalvik.vm.heapminfree]: [512k]
- * [dalvik.vm.heapsize]: [512m]
- * [dalvik.vm.heapstartsize]: [8m]
- * [dalvik.vm.heaptargetutilization]: [0.75]
其中 dalvik.vm.heapsize 是最大可以使用的内存, 这个数值同厂商跟版本都有关系, 随着配置的提高, 都在逐渐增大, 既然虚拟机能使用的最大内存是 dalvik.vm.heapsize, 那么在申请内存的时候是不是一直到最大值才会 GC 呢? 答案肯定是否定的, 从我们检测的曲线来看, 在内存使用很低的时候, 也会 GC, 看下图 App 运行时情况:
从上图看到, 1,2,3 这三个点好像是都发生了 GC, 但是这个时候, App 内存的占用并不是很高, 距离最大内存还有很远, 那么这个时候为什么会发生内存 GC 呢, 其实直观上也比较好理解, 如果一直等到最大内存才 GC, 那么就会有两个弊端: 首先, 内存资源浪费, 造成系统性能降低, 其次, GC 时内存占用越大, 耗时越长, 应尽量避免. 那 GC 的时机到底是什么时候呢? 是不是每次内存块分配的时候都会 GC, 这个应该也是否定的, 本文就来简单的了解下内存分配, GC, 内存增长等机制.
Android Dalvik 虚拟机分配及 GC
首先看一下虚拟机的配置参数的意义, 上面只讲述了 dalvik.vm.heapstartsize, 是最大内存申请尺寸,
dalvik.vm.heapgrowthlimit 和 dalvik.vm.heapsize 都是 java 虚拟机的最大内存限制, 一般 heapgrowthlimit<heapsize, 如果在 Manifest 中的 application 标签中声明 Android:largeHeap="true",App 直到 heapsize 才 OOM, 否则达到 heapgrowthlimit 就 OOM
dalvik.vm.heapstartsize Java 堆的起始大小, 指定了 Davlik 虚拟机在启动的时候向系统申请的物理内存的大小, 后面再根据需要逐渐向系统申请更多的物理内存, 直到达到 MAX
dalvik.vm.heapminfree 堆最小空闲值, GC 后
dalvik.vm.heapmaxfree 堆最大空闲值
dalvik.vm.heaptargetutilization 堆目标利用率
后面三个值用来确保每次 GC 之后 Java 堆已经使用和空闲的内存有一个合适的比例, 这样可以尽量地减少 GC 的次数, 堆的利用率为 U, 最小空闲值为 MinFree 字节, 最大空闲值为 MaxFree 字节, 假设在某一次 GC 之后, 存活对象占用内存的大小为 LiveSize. 那么这时候堆的理想大小应该为 (LiveSize / U). 但是(LiveSize / U) 必须大于等于 (LiveSize + MinFree) 并且小于等于(LiveSize + MaxFree), 否则, 就要进行调整, 调整的其实是软上限 softLimit,
- static size_t getUtilizationTarget(const HeapSource* hs, size_t liveSize)
- {
- size_t targetSize = (liveSize / hs->targetUtilization) * HEAP_UTILIZATION_MAX;
- if (targetSize> liveSize + hs->maxFree) {
- targetSize = liveSize + hs->maxFree;
- } else if (targetSize <liveSize + hs->minFree) {
- targetSize = liveSize + hs->minFree;
- }
- return targetSize;
- }
以上就是计算公式的源码, 假设 liveSize = 150M,targetUtilization=0.75,maxFree=8,minFree=512k, 那么理想尺寸 200M, 而 200M 很明显超过了 150+8, 那么这个时候, 堆的尺寸就应该调整到 158M, 这个 softLimit 软上限也是下次申请内存时候是否需要 GC 的一个重要指标, 请看以下场景:
场景一: 当前 softLimit=158M,liveSize = 150M, 如果这个时候, 需要分配一个 100K 内存的对象
由于当前的上限是 158M, 内存是可以直接分配成功的, 分配之后, 由于空闲内存 8-100K>512k, 也不需要调整内存, 这个时候, 不存在 GC,
场景二: 当前 softLimit=158M,liveSize = 150M, 如果这个时候, 需要分配的内存是 7.7M
由于当前的上限是 158M, 内存是可以直接分配成功的, 分配之后, 由于空闲内存 8-7.7M <512k, 那就需要 GC, 同时调整 softLimit
场景三: 当前 softLimit=158M,liveSize = 150M, 如果这个时候, 需要分配的内存是 10M
由于当前的上限是 158M, 内存分配失败, 需要先 GC,GC 之后调整 softLimit, 再次请求分配, 如果还是失败, 将 softLimit 调整为最大, 再次请求分配, 失败就再 GC 一次软引用, 再次请求, 还是失败那就是 OOM, 成功后要调整 softLimit
所以, Android 在申请内存的时候, 可能先分配, 也可能先 GC, 也可能不 GC, 这里面最关键的点就是内存利用率跟 Free 内存的上下限, 下面简单看源码了解下堆内存分配流程:
- static void *tryMalloc(size_t size)
- {
- void *ptr;
- <!--1 首次请求分配内存 -->
- ptr = dvmHeapSourceAlloc(size);
- if (ptr != NULL) {
- return ptr;
- }
- <!--2 分配失败, GC-->
- if (gDvm.gcHeap->gcRunning) {
- dvmWaitForConcurrentGcToComplete();
- } else {
- gcForMalloc(false);
- }
- <!-- 再次分配 -->
- ptr = dvmHeapSourceAlloc(size);
- if (ptr != NULL) {
- return ptr;
- }
- <!-- 还是分配失败, 调整 softLimit 再次分配 -->
- ptr = dvmHeapSourceAllocAndGrow(size);
- if (ptr != NULL) {
- size_t newHeapSize;
- <!-- 分配成功后要调整 softLimit-->
- newHeapSize = dvmHeapSourceGetIdealFootprint();
- return ptr;
- }
- <!-- 还是分配失败, GC 力加强, 回收 soft 引用,-->
- gcForMalloc(true);
- <!-- 再次请求分配, 如果还是失败, 那就 OOM 了 -->
- ptr = dvmHeapSourceAllocAndGrow(size);
- if (ptr != NULL) {
- return ptr;
- }
- dvmDumpThread(dvmThreadSelf(), false); return NULL;
- }
总结
本文主要说的一个问题就是, 为什么不等到最大内存在 GC, 以及普通 GC 的可能时机, 当然, 对于内存的 GC 是更加复杂的, 不在本文的讨论范围之内, 同时这个也解释频繁的分配大内存会导致 GC 抖动的原因, 毕竟, 如果你超过了 maxFree , 就一定 GC, 有兴趣可以自行深入分析.
自己是从事了七年开发的 Android 工程师, 不少人私下问我, 2019 年 Android 进阶该怎么学, 方法有没有?
没错, 年初我花了一个多月的时间整理出来的学习资料, 希望能帮助那些想进阶提升 Android 开发, 却又不知道怎么进阶学习的朋友.[包括高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发(ReactNative+Weex),Flutter 等架构技术资料] , 希望能帮助到您面试前的复习且找到一个好的工作, 也节省大家在网上搜索资料的时间来学习.
来源: http://www.jianshu.com/p/623d50ffb7f4