Android 虚拟机中的内存分配与OOM问题详解

目录
  • 背景知识
  • 一、Android VM的内存空间
    • 1.查看内存的API
  • 二、Android VM内存分配流程
    • 小结
  • 三、出现OOM的建议解决方案

背景知识

Android中每个App默认情况下是运行在一个独立进程中的, 而这个独立进程正是从Zygote孵化出来的VM进程, 也就是说, 也就是说每个Android APP在运行时会启动一个Java虚拟机。

并且系统会给它分配固定的内存空间(手机厂商会根据手机的配置情况来对其进行调整)。

一、Android VM的内存空间

Android是一个多任务系统, 为了保证多任务的运行, Android给每个App可使用的Heap大小设定了一个限定值.

这个值是系统设置的prop值, 保存在System/build.prop文件中. 一般国内的手机厂商都会做修改, 根据手机配置不同而不同, 可以直接打开查看与修改。

其中和虚拟机内存相关的主要有以下三个:

1 . dalvik.vm.heapstartsize

– App启动后,系统分配给它的Heap初始大小,随着App使用可增加。

2 . dalvik.vm.heapgrowthlimit

– 默认情况下, App可使用的Heap的最大值, 超过这个值就会产生OOM.

3 . dalvik.vm.heapsize

– 如果App的manifest文件中配置了largeHeap属性, 那么App可使用的Heap的最大值为此项设定值。

 <application
    android:largeHeap="true">
    ...
</application>

所以对于同一个手机,不开启largeHeap属性时与多进程时,每个APP的虚拟机分配的内存的上限都是heapgrowthlimit

1.查看内存的API

Android在ActivityManager类中提供了API可以运行时获取这些属性值,如下:

//ActivityManager的getMemoryClass()获得内用正常情况下内存的大小,即heapgrowthlimit的值
//ActivityManager的getLargeMemoryClass()可以获得开启largeHeap最大的内存大小,即heapsize的指
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryClass();
activityManager.getLargeMemoryClass();

二、Android VM内存分配流程

虚拟机分配内存的具体源码可以AOSP的Heap.cpp文件中查看:

/* Try as hard as possible to allocate some memory.
 */
static void *tryMalloc(size_t size)
{
    void *ptr;
//TODO: figure out better heuristics
//    There will be a lot of churn if someone allocates a bunch of
//    big objects in a row, and we hit the frag case each time.
//    A full GC for each.
//    Maybe we grow the heap in bigger leaps
//    Maybe we skip the GC if the size is large and we did one recently
//      (number of allocations ago) (watch for thread effects)
//    DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other
//      (or, at least, there are only 0-5 objects swept each time)
    ptr = dvmHeapSourceAlloc(size);
    if (ptr != NULL) {
        return ptr;
    }
    /*
     * The allocation failed.  If the GC is running, block until it
     * completes and retry.
     */
    if (gDvm.gcHeap->gcRunning) {
        /*
         * The GC is concurrently tracing the heap.  Release the heap
         * lock, wait for the GC to complete, and retrying allocating.
         */
        dvmWaitForConcurrentGcToComplete();
    } else {
      /*
       * Try a foreground GC since a concurrent GC is not currently running.
       */
      gcForMalloc(false);
    }
    ptr = dvmHeapSourceAlloc(size);
    if (ptr != NULL) {
        return ptr;
    }
    /* Even that didn't work;  this is an exceptional state.
     * Try harder, growing the heap if necessary.
     */
    ptr = dvmHeapSourceAllocAndGrow(size);
    if (ptr != NULL) {
        size_t newHeapSize;
        newHeapSize = dvmHeapSourceGetIdealFootprint();
//TODO: may want to grow a little bit more so that the amount of free
//      space is equal to the old free space + the utilization slop for
//      the new allocation.
        LOGI_HEAP("Grow heap (frag case) to "
                "%zu.%03zuMB for %zu-byte allocation",
                FRACTIONAL_MB(newHeapSize), size);
        return ptr;
    }
    /* Most allocations should have succeeded by now, so the heap
     * is really full, really fragmented, or the requested size is
     * really big.  Do another GC, collecting SoftReferences this
     * time.  The VM spec requires that all SoftReferences have
     * been collected and cleared before throwing an OOME.
     */
//TODO: wait for the finalizers from the previous GC to finish
    LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation",
            size);
    gcForMalloc(true);
    ptr = dvmHeapSourceAllocAndGrow(size);
    if (ptr != NULL) {
        return ptr;
    }
//TODO: maybe wait for finalizers and try one last time
    LOGE_HEAP("Out of memory on a %zd-byte allocation.", size);
//TODO: tell the HeapSource to dump its state
    dvmDumpThread(dvmThreadSelf(), false);
    return NULL;
}

具体流程如下:

  • 尝试分配,如果成功则返回,失败则转入步骤2
  • 判断是否gc正在进行垃圾回收,如果正在进行则等待回收完成之后,尝试分配。如果成功则返回,失败则转入步骤3
  • 自己启动gc进行垃圾回收,这里gcForMalloc的参数是false。所以不会回收软引用,回收完成后尝试分配,如果成功则返回,失败则转入步骤4
  • 调用dvmHeapSourceAllocAndGrow尝试分配,这个函数会扩张堆的大小,失败转入步骤5
  • 进入回收软引用阶段,这里gcForMalloc的参数是ture,所以需要回收软引用。然后再调用dvmHeapSourceAllocAndGrow尝试分配,如果失败则抛出OOM

小结

所以产生OOM时,一定是java的堆中 已有的内存 + 申请的内存 >= heapgrowthlimit导致的,不会因为手机目前物理内存是否紧张而改变 - 当物理内存非常紧张时系统会通过LowMemory Killer杀掉一些低优先级的进程。

相应的,物理内存非常充足的情况也会有OOM的情况发生。

三、出现OOM的建议解决方案

当APP出现OOM时,建议可以从以下两个方向来处理:

1 . 排查内存泄露问题

  • 排查各个功能是否内存泄露情况,可以通过Android Studio中的MemoryMonitor功能进行分析,Memory Monitor也集成了HPROF Viewer和Allocation Tracker可以分析内存快照与内存分配追踪。另外推荐一个工具,square公司开源的leakcanary,非常简洁好用。
  • 排查进程初始化时就直接申请并常驻内存的对象以及其他功能里申请的static对象或者单例对象的必要性。

2 . 内存优化

按照谷歌在youtube上发布的性能优化典范之内存篇,优化各功能的内存,或可参照 胡凯的总结 。

大致有以下这些,具体请参见原文:

  • 谨慎使用large heap
  • 综合考虑设备的内存阈值与其他因素设计合适的缓存大小
  • onLowMemory与onTrimMemory
  • 资源文件需要选择合适的文件夹进行存放
  • Try catch某些大内存分配的操作
  • 谨慎使用static对象
  • 特别留意单例对象中不合理的持有
  • 珍惜Services资源
  • 优化布局层次,减少内存消耗
  • 谨慎使用“抽象”编程
  • 使用nano protobufs序列化数据
  • 谨慎使用依赖注入框架
  • 谨慎使用多进程
  • 使用ProGuard来剔除不需要的代码
  • 谨慎使用第三方libraries

考虑不同的实现方式来优化内存占用

  • 注意Activity的泄露
  • 考虑使用Applicaiton Context代替Activity Context
  • 注意临时Bitmap对象的及时回收
  • 注意监听器的注销
  • 注意缓存容器里的对象泄露
  • 注意Webview的泄露

注意Cursor对象的及时关闭

  • 复用系统自带的资源
  • ListView中对ConvertView的复用
  • Bitmap对象的复用
  • 避免在ondraw方法里执行对象的创建

StringBuilder代替String

  • 使用更加轻量的数据结构
  • 避免在Android里使用enum
  • 减少Bitmap对象的内存占用

使用更小的图片

  • 减少对象的内存占用
  • 内存对象的重复利用
  • 避免对象的内存泄露
  • 内存使用策略的优化

以上就是Android 虚拟机中的内存分配与OOM问题详解的详细内容,更多关于Android虚拟机内存分配OOM的资料请关注我们其它相关文章!

(0)

相关推荐

  • unity3d发布apk在android虚拟机中运行的详细步骤(unity3d导出android apk)

    unity3d发布apk在android虚拟机中运行的详细步骤(unity3d导出android apk),总的流程分为以下6个步骤: 1.安装java_jdk 2.配置java环境变量 3.更新android的sdk 4.从Unity3d中发布出apk文件 5.创建android虚拟机并运行 6.将apk文件安装到android虚拟机中 (为方便新手,在下面对每个步骤的具体操作及可能遇到的问题详细提一下) 1.安装java_jdk 官网(www.java.com),免费,我安装的文件的名字是j

  • 解决android关于打开虚拟机时右侧工具栏不显示的问题

    一开始我就纳闷了,怎么调试都只是一个光溜溜的界面,右侧的工具栏都没有 如图: 就一个光秃秃的界面,什么都没有,这就对调试很不方便 于是我就试了试各种方法,然而并没有什么卵用. 后来一次无意之后发现我好像皮肤没选中即: 这就是问题的关键所在,当你没有选择的皮肤为no skin时,它的工具栏就不会出来 所以解决办法是: 将皮肤随便选一个就行了,但就是不能选no skin 以上这篇解决android关于打开虚拟机时右侧工具栏不显示的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多

  • vmware虚拟机安装安卓Android x86的方法步骤

    有时候只是想测试一个app,又不想在手机上做个测试,这个时候我们就可以用虚拟机来完成这件事情.首先到官网上去下载一个安卓系统(https://www.android-x86.org/),我这里用:android-x86-9.0-rc1.iso做演示. 或者选择本地安卓系统下载地址:https://www.jb51.net/softs/203311.html 在提供一个VMware15的下载地址:https://www.jb51.net/softs/638385.html VMware15 for

  • Android虚拟机与类加载机制详情

    目录 JVM与Dalvik 基于栈的虚拟机 字节码指令 执行过程 基于寄存器的虚拟机 寄存器 基于寄存器的虚拟机 ART与Dalvik dex2aot dexopt与dexaot Android N的运作方式 ClassLoader 介绍 ClassLoader加载流程与双亲委托机制 类加载 热修复 JVM与Dalvik Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例.Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的

  • Android APK文件在电脑(PC虚拟机)上面运行方法

    APK是Android系统的发布的工程包,很多时候我们想在电脑上而非Android手机上面运行它.下面就提供下Android APK文件在电脑上面运行方法. 首先要配置android环境变量,如果配置好了,安装就会很方便了. 可以在系统变量的path下追加,也可以在用户变量的path下追加,区别应该都知道的. 追加时不要忘了先用英文分号";"分隔前后环境变量值.添加android环境变量的值时要注意,adb.exe已经从tools目录转移到platform-tools目录了,具体可以看

  • Android studio虚拟机在启动界面和桌面出现画面模糊花屏问题的解决方法

    1.之前自己的虚拟机这样设置没问题,今天突然出现这样的花屏 2.最后解决了,解决方法,按照下边红框设置 3.问题方法说明: 之前看不太明白,就找的翻译 反思:之前是设置的自动(默认是硬件渲染)可以,今天突然出现花屏那样的问题,搜遍全网没找到方法,最后有个大佬指点按照上边的方法设置(软件渲染)配置虚拟机,解决. 可能自己电脑硬件图形卡(不知道什么东东)哪里出了问题,也可能不小心更新组件后,android studio的设置出问题啦. 总结 到此这篇关于Android studio虚拟机在启动界面和

  • Android编程图片加载类ImageLoader定义与用法实例分析

    本文实例讲述了Android编程图片加载类ImageLoader定义与用法.分享给大家供大家参考,具体如下: 解析: 1)图片加载使用单例模式,避免多次调用时产生死锁 2)核心对象 LruCache 图片加载时先判断缓存里是否有图片,如果有,就使用缓存里的 没有就加载网络的,然后置入缓存 3)使用了线程池ExecutorService mThreadPool技术 4)使用了Semaphore 信号来控制变量按照先后顺序执行,避免空指针的问题 如何使用: 在Adapter里加载图片时 复制代码 代

  • 详解Android类加载ClassLoader

    基本知识 Java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全.所谓双亲代理就是指,当加载类的时候首先去Bootstrap中加载类,如果没有则去Extension中加载,如果再没有才去AppClassLoader中去加载.从而实现安全和稳定. Java ClassLoader BootstrapClassLoader 引导类加载器 ,用来加载Java的核心库.通过底层代码来实现的,基本上只要parent为null,那就表示引导类加载器. 比如:charsets

  • AndroidStudio中AVD虚拟机设备空间不足调试过程出现的黑屏问题及解决方案

    AVD出现空间不足时,会造成一些功能没有完全呈现出来,给开发人员调试会出现一个很大的问题,不能直观地看到调试的结果. 所以,必须将AVD的存储空间配置好,才能够保证开发过程少出错误. 下面找路径 C:\Users\Administrator\.android\avd\ 说明: 路径一般为.android默认的路径,比如我的就是C:\Users\Administrator\.android\avdavd目录下,放的是你已经创建的虚拟机设备,比如我现在要修改设备空间的是AVD.avd 就可以直接进入

  • android 使用虚拟机安装apk(图文教程)

    1.启动虚拟机 2.我的android sdk在"E:\android\android开发环境\android SDK\platform-tools"把要安装的apk复制到这个根目录(和adb.exe同文件夹). 2.点击电脑左下角的"开始"按钮,输入cmd然后点击回车打开cmd输入框. 3. (由于我的Android SDK安装路径为e盘,所以需要定位模拟器platform-tools目录) 在cmd命令提示符中输入E: 回车,如图 第一行, 4.apk名称为zq

随机推荐