oom的哪些事儿

Android开发中常常会遇到out of memory error 异常,并且这种异常的发生原因是比较难发现的,因为这种异常往往不是必现的。在一个手机上出现,换个手机有可能又不出现,在同一个手机也不是必现。这篇文章我们来探讨一下Android系统在什么情况下会抛这个异常,并且我们该怎么避免这个异常

1、oom发生的时机

art(Dalvik)虚拟机会为app设置堆内存使用上限,当app剩余可使用的内存小于申请的内存就会触发out of memory error。还有一种情况就是在创建线程时,创建线程时有三种情况会触发oom。在创建线程都需要先申请一段内存作为该线程的栈空间,一旦整个系统剩余的内存空间不足于提供创建线程所需要的内存空间时就会发生oom。这种情况和虚拟机触发的oom不同,虚拟机是app进程的堆内存耗尽时就会触发oom,这个时候有可能系统还有很多剩余的内存,而创建线程这种是整个系统的内存都被用完了才会发生oom;一般来说Android中主线程需要的栈空间是8M,其他子线程默认需要的栈空间是1m,也可以在创建子线程是指定栈的大小

    /* @param  group
     *         the thread group. If {@code null} and there is a security
     *         manager, the group is determined by {@linkplain
     *         SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}.
     *         If there is not a security manager or {@code
     *         SecurityManager.getThreadGroup()} returns {@code null}, the group
     *         is set to the current thread's thread group.
     *
     * @param  target
     *         the object whose {@code run} method is invoked when this thread
     *         is started. If {@code null}, this thread's run method is invoked.
     *
     * @param  name
     *         the name of the new thread
     *
     * @param  stackSize
     *         the desired stack size for the new thread, or zero to indicate
     *         that this parameter is to be ignored.
     *
     * @throws  SecurityException
     *          if the current thread cannot create a thread in the specified
     *          thread group
     *
     * @since 1.4
     */
    public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }

第四个参数就是指定栈的大小。可以粗略的计算一下,在一个32位的手机中默认最多可以创建的线程数为:(2^32 - 8 * 1024 * 1024) / (1024 * 1024),实际上最大的线程数肯定会小于这个值,因为除了上述所说的内存的限制,linux内核对最大线程数也做了限制,这个数值一般是由手机厂商定制的,根据我遇到的手机,发现华为的手机这个值设置的相对较小,大概是500左右。创建线程的第二个发生oom的地方就是当线程数大于系统限制的最新线程数量时,linux创建线程需要创建一个FileDescriptor,当进程中的fd数量达到最大值时也会触发oom

2、减少oom的一些建议

上一节说了发生oom的时机,这一节来说说如果减少发生oom的概率。对于由于堆内存不足虚拟机触发的oom,我们应该及时回收不再使用的对象,避免造成内存泄漏,如果发生内存泄漏可以做一些补救措施,如何补救请看参考我的另一篇文章关于内存泄漏的一些思考

Android中最消耗的内存就是bitmap,bitmap内存由两部分组成,一部分是bitmap对象所需要的内存,这部分所需内存一般不大和其他对象差不多,另一部分是保存图片资源的byte数组,在Android3.0之前的系统中这部分内存放在native中,这是不占用堆内存的。但是3.0之前是在bitmap

 * @throws Throwable the {@code Exception} raised by this method
     * @see java.lang.ref.WeakReference
     * @see java.lang.ref.PhantomReference
     * @jls 12.6 Finalization of Class Instances
     */
    protected void finalize() throws Throwable { }

中释放图片所以占用的byte数组,由于finalize方法执行时机的不确定性可能导致bitmap对象被销毁之后,图片资源还很长一段时间没有被释放,这种情况必须在确定bitmap不再需要被使用时手动调用bitmap的recyle方法。

正是由于这个原因Android3.0之后系统把图片资源的byte数组直接放到了应用进程的堆中,当然由于堆内存的大小有限制可能会导致在系统还有很多内存时发生oom。在Android8.0之后又把资源的byte数组放回到native中,这次Android系统用了一中比较厉害的机制来保证图片资源及时被回收,这个机制具体是怎么运作的目前没有研究。

我们的手机的显示区域有限,很多时候不需要把整张图片都加载到内存中,可以根据展示区域的大小来对图片进行采样

/**
         * If set to a value > 1, requests the decoder to subsample the original
         * image, returning a smaller image to save memory. The sample size is
         * the number of pixels in either dimension that correspond to a single
         * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
         * an image that is 1/4 the width/height of the original, and 1/16 the
         * number of pixels. Any value <= 1 is treated the same as 1. Note: the
         * decoder uses a final value based on powers of 2, any other value will
         * be rounded down to the nearest power of 2.
         */
        public int inSampleSize;

通过设置inSampleSize来决定图片的采样率,这个值只能是2的n次方

监测app的剩余堆内存,当剩余的堆内存达到某个阀值时释放app的缓存。还可以在app统一bitmap创建的入口,根据不同的手机来决定要创建bitmap的像素格式,高端手机中可以使用argb8888,低端一点的手机使用rgb565,同样大小的图片argb8888是rgb565的两倍

对于创建线程导致的oom,可以在app中用统一的线程池,统一的ThreadFactory。利用 linux 的 inotify 机制进行监控:
watch /proc/pid/fd来监控 app 打开文件的情况,
watch /proc/pid/task来监控线程使用情况.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,097评论 1 32
  • 摘要: 本文发现了一类OOM(OutOfMemoryError),这类OOM的特点是崩溃时java堆内存和设备物理...
    陶菜菜阅读 50,555评论 51 355
  • 所有知识点已整理成app app下载地址 J2EE 部分: 1.Switch能否用string做参数? 在 Jav...
    侯蛋蛋_阅读 2,429评论 1 4
  • 人生中,总是累多于美 所以我们不得不面对 感情里,总是疼超过醉 所以我们难免有心碎 理不清的是是非非 只能独自去品...
    人间地狱天堂阅读 214评论 0 0
  • “我从内心是深爱我的学生的。因为每个人都会爱屋及乌。我是一名母亲,我深深地爱着我的孩子。当我把自己的孩子送...
    水寨小学武辉锋阅读 1,035评论 1 6