oom的哪些事兒

Android開發(fā)中常常會遇到out of memory error 異常,并且這種異常的發(fā)生原因是比較難發(fā)現(xiàn)的,因為這種異常往往不是必現(xiàn)的。在一個手機上出現(xiàn),換個手機有可能又不出現(xiàn),在同一個手機也不是必現(xiàn)。這篇文章我們來探討一下Android系統(tǒng)在什么情況下會拋這個異常,并且我們該怎么避免這個異常

1、oom發(fā)生的時機

art(Dalvik)虛擬機會為app設(shè)置堆內(nèi)存使用上限,當(dāng)app剩余可使用的內(nèi)存小于申請的內(nèi)存就會觸發(fā)out of memory error。還有一種情況就是在創(chuàng)建線程時,創(chuàng)建線程時有三種情況會觸發(fā)oom。在創(chuàng)建線程都需要先申請一段內(nèi)存作為該線程的棧空間,一旦整個系統(tǒng)剩余的內(nèi)存空間不足于提供創(chuàng)建線程所需要的內(nèi)存空間時就會發(fā)生oom。這種情況和虛擬機觸發(fā)的oom不同,虛擬機是app進(jìn)程的堆內(nèi)存耗盡時就會觸發(fā)oom,這個時候有可能系統(tǒng)還有很多剩余的內(nèi)存,而創(chuàng)建線程這種是整個系統(tǒng)的內(nèi)存都被用完了才會發(fā)生oom;一般來說Android中主線程需要的??臻g是8M,其他子線程默認(rèn)需要的棧空間是1m,也可以在創(chuàng)建子線程是指定棧的大小

    /* @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);
    }

第四個參數(shù)就是指定棧的大小??梢源致缘挠嬎阋幌拢谝粋€32位的手機中默認(rèn)最多可以創(chuàng)建的線程數(shù)為:(2^32 - 8 * 1024 * 1024) / (1024 * 1024),實際上最大的線程數(shù)肯定會小于這個值,因為除了上述所說的內(nèi)存的限制,linux內(nèi)核對最大線程數(shù)也做了限制,這個數(shù)值一般是由手機廠商定制的,根據(jù)我遇到的手機,發(fā)現(xiàn)華為的手機這個值設(shè)置的相對較小,大概是500左右。創(chuàng)建線程的第二個發(fā)生oom的地方就是當(dāng)線程數(shù)大于系統(tǒng)限制的最新線程數(shù)量時,linux創(chuàng)建線程需要創(chuàng)建一個FileDescriptor,當(dāng)進(jìn)程中的fd數(shù)量達(dá)到最大值時也會觸發(fā)oom

2、減少oom的一些建議

上一節(jié)說了發(fā)生oom的時機,這一節(jié)來說說如果減少發(fā)生oom的概率。對于由于堆內(nèi)存不足虛擬機觸發(fā)的oom,我們應(yīng)該及時回收不再使用的對象,避免造成內(nèi)存泄漏,如果發(fā)生內(nèi)存泄漏可以做一些補救措施,如何補救請看參考我的另一篇文章關(guān)于內(nèi)存泄漏的一些思考。

Android中最消耗的內(nèi)存就是bitmap,bitmap內(nèi)存由兩部分組成,一部分是bitmap對象所需要的內(nèi)存,這部分所需內(nèi)存一般不大和其他對象差不多,另一部分是保存圖片資源的byte數(shù)組,在Android3.0之前的系統(tǒng)中這部分內(nèi)存放在native中,這是不占用堆內(nèi)存的。但是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數(shù)組,由于finalize方法執(zhí)行時機的不確定性可能導(dǎo)致bitmap對象被銷毀之后,圖片資源還很長一段時間沒有被釋放,這種情況必須在確定bitmap不再需要被使用時手動調(diào)用bitmap的recyle方法。

正是由于這個原因Android3.0之后系統(tǒng)把圖片資源的byte數(shù)組直接放到了應(yīng)用進(jìn)程的堆中,當(dāng)然由于堆內(nèi)存的大小有限制可能會導(dǎo)致在系統(tǒng)還有很多內(nèi)存時發(fā)生oom。在Android8.0之后又把資源的byte數(shù)組放回到native中,這次Android系統(tǒng)用了一中比較厲害的機制來保證圖片資源及時被回收,這個機制具體是怎么運作的目前沒有研究。

我們的手機的顯示區(qū)域有限,很多時候不需要把整張圖片都加載到內(nèi)存中,可以根據(jù)展示區(qū)域的大小來對圖片進(jìn)行采樣

/**
         * 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;

通過設(shè)置inSampleSize來決定圖片的采樣率,這個值只能是2的n次方

監(jiān)測app的剩余堆內(nèi)存,當(dāng)剩余的堆內(nèi)存達(dá)到某個閥值時釋放app的緩存。還可以在app統(tǒng)一bitmap創(chuàng)建的入口,根據(jù)不同的手機來決定要創(chuàng)建bitmap的像素格式,高端手機中可以使用argb8888,低端一點的手機使用rgb565,同樣大小的圖片argb8888是rgb565的兩倍

對于創(chuàng)建線程導(dǎo)致的oom,可以在app中用統(tǒng)一的線程池,統(tǒng)一的ThreadFactory。利用 linux 的 inotify 機制進(jìn)行監(jiān)控:
watch /proc/pid/fd來監(jiān)控 app 打開文件的情況,
watch /proc/pid/task來監(jiān)控線程使用情況.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,665評論 1 32
  • 摘要:?本文發(fā)現(xiàn)了一類OOM(OutOfMemoryError),這類OOM的特點是崩潰時java堆內(nèi)存和設(shè)備物理...
    陶菜菜閱讀 51,709評論 51 357
  • 所有知識點已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,710評論 1 4
  • 人生中,總是累多于美 所以我們不得不面對 感情里,總是疼超過醉 所以我們難免有心碎 理不清的是是非非 只能獨自去品...
    人間地獄天堂閱讀 257評論 0 0
  • “我從內(nèi)心是深愛我的學(xué)生的。因為每個人都會愛屋及烏。我是一名母親,我深深地愛著我的孩子。當(dāng)我把自己的孩子送...
    水寨小學(xué)武輝鋒閱讀 1,192評論 1 6

友情鏈接更多精彩內(nèi)容