IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 记一次Android OOM问题 -> 正文阅读

[移动开发]记一次Android OOM问题

一.问题背景:

新平台固件测试后,反馈一个OOM的问题。问题日志如下:

3944  3959 I: Clamp target GC heap from 280MB to 256MB
3944  3959 I: Alloc concurrent copying GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 256MB/256MB, paused 173us total 383.632ms
3944  3944 I: WaitForGcToComplete blocked Alloc on HeapTrim for 4.630s
3944  3944 I: Starting a blocking GC Alloc
3944  3944 I: Starting a blocking GC Alloc
3944  3958 I: Waiting for a blocking GC Alloc
3944  4712 I: Waiting for a blocking GC Alloc
3944  3959 W: Throwing OutOfMemoryError "Failed to allocate a 144 byte allocation with 0 free bytes and 0B until OOM, target footprint 268435456, growth limit 268435456" (VmSize 1555288 kB, recursive case)
3944  3959 W: "FinalizerWatchdogDaemon" daemon prio=5 tid=9 Runnable
3944  3959 W:   | group="system" sCount=0 dsCount=0 flags=2 obj=0x12c433c8 self=0xa8422000
3944  3959 W:   | sysTid=3959 nice=4 cgrp=default sched=0/0 handle=0x89ae3230
3944  3959 W:   | state=R schedstat=( 16335080529 328336875 3663 ) utm=1366 stm=266 core=2 HZ=100
3944  3959 W:   | stack=0x899e0000-0x899e2000 stackSize=1040KB
3944  3959 W:   | held mutexes= "mutator lock"(shared held)
3944  3959 W:   at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:68)
3944  3959 W:   at java.lang.StringBuilder.<init>(StringBuilder.java:90)
3944  3959 W:   at com.android.internal.os.RuntimeInit$LoggingHandler.uncaughtException(RuntimeInit.java:91)
3944  3959 W:   at java.lang.Thread.dispatchUncaughtException(Thread.java:2181)
3944  3959 W:   at java.lang.Daemons$FinalizerWatchdogDaemon.finalizerTimedOut(Daemons.java:483)
3944  3959 W:   at java.lang.Daemons$FinalizerWatchdogDaemon.runInternal(Daemons.java:325)
3944  3959 W:   at java.lang.Daemons$Daemon.run(Daemons.java:137)
3944  3959 W:   at java.lang.Thread.run(Thread.java:919)
3944  3959 W: 
3944  3959 I Process : Sending signal. PID: 3944 SIG: 9

测试代码:

void test() {
    byte[] ucKey = {0, 1, 2, 3, 4, 5, -1};
    for (int i = 0; i < ucKey.length; i++) {
        for (int j = 0; j < 100000; j++) {
            if (!stopTest()) {
                Display.setInputResult(String.format("i=%d, j=%d\n", Integer.valueOf(i), Integer.valueOf(j)));
                try {
                    testFunctionInterface(ucKey[i]); // 这里是一个被测试的接口,测试反馈该接口压力测试时出现OOM
                } catch (Exception e) {
                    Display.printException(e);
                    fail(String.format("testFunctionInterface fail iRet=%d, ucKey=%d, j=%d\n", Integer.valueOf(e.exceptionCode), Byte.valueOf(ucKey[i]), Integer.valueOf(j)));
                }
            } else {
                return;
            }
        }
    }
}

二.问题分析

从日志可以明显看出是内存溢出,但是溢出是哪里造成的,从代码难以看出,这个时候就需要使用内存分析工具mat了。

1.抓取heap

可以使用命令行抓取

adb shell
am dumpheap 包名 /sdcard/dump.hprof

也可以使用Android Studio抓取:

打开"profilter"面板,选择需要分析的应用(注意这里应用如果是release的是没办法抓取的),点击"Memeory",选择"Capture heap dump"

接下来另存为抓取的heap,此时的heap还无法使用mat分析,需要进行转换

找到android sdk的platform-tools目录,有个hprof-conv.exe

hprof-conv.exe 源文件 目标文件

2.使用MAT分析

.下载MAT工具:网址?http://www.eclipse.org/mat/downloads.php

打开"MemoryAnalyzer.exe", File-Open Heap Dump

打开Leak Suspects

只看强应用对象

平时使用mat分析,一般都有多个可以点,然后需要自己一一排查,这次居然只有这么一个点,真的太幸运了。

?三.结合源码分析

经过上面的步骤我们可以发现溢出是在

android.view.textservice.SpellCheckerSession$SpellCheckerSessionListenerImpl

再次回到测试代码,Display.setInputResult是一个EditText用于显示内容,而被测接口跟view无关,我们去掉测试接口,再测试,发现仍会内存溢出,这里很明显是我们的EditText导致的。

这么看来是我们系统的拼写检查问题了,打开相关日志:

frameworks/base/core/java/android/service/textservice/SpellCheckerService.java
frameworks/base/core/java/android/view/textservice/SpellCheckerSession.java
frameworks/base/core/java/android/view/textservice/TextServicesManager.java
frameworks/base/services/core/java/com/android/server/textservices/TextServicesManagerService.java

使用新的固件再来看看:

D/SpellCheckerSession: queueing tasks in processOrEnqueueTask since the connection is not established. mPendingTasks.size()=1
W/TextServicesManagerService: bind service: com.android.inputmethod.latin/.spellcheck.AndroidSpellCheckerService
D/SpellCheckerSession: queueing tasks in processOrEnqueueTask since the connection is not established. mPendingTasks.size()=2
D/SpellCheckerSession: queueing tasks in processOrEnqueueTask since the connection is not established. mPendingTasks.size()=3
D/SpellCheckerSession: queueing tasks in processOrEnqueueTask since the connection is not established. mPendingTasks.size()=4
...
W/SpellCheckerService: onBind
W/TextServicesManagerService: getCurrentSpellCheckerSubtype: 0
W/TextServicesManagerService: onServiceConnectedInnerLocked: ComponentInfo{com.android.inputmethod.latin/com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService}
W/Binder: Caught a RuntimeException from the binder stub implementation.
    java.lang.SecurityException: Failed to find provider user_dictionary for user 0; expected to find a valid ContentProvider for this authority
        at android.os.Parcel.createException(Parcel.java:2071)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.os.Parcel.readException(Parcel.java:1987)
        at android.content.IContentService$Stub$Proxy.registerContentObserver(IContentService.java:1234)
        at android.content.ContentResolver.registerContentObserver(ContentResolver.java:2263)
        at android.content.ContentResolver.registerContentObserver(ContentResolver.java:2251)
        at com.android.inputmethod.latin.spellcheck.AndroidWordLevelSpellCheckerSession.<init>(AndroidWordLevelSpellCheckerSession.java:111)
        at com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerSession.<init>(AndroidSpellCheckerSession.java:43)
        at com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerSessionFactory.newInstance(AndroidSpellCheckerSessionFactory.java:23)
        at com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.createSession(AndroidSpellCheckerService.java:138)
        at android.service.textservice.SpellCheckerService$SpellCheckerServiceBinder.getISpellCheckerSession(SpellCheckerService.java:339)
        at com.android.internal.textservice.ISpellCheckerService$Stub.onTransact(ISpellCheckerService.java:110)
        at android.os.Binder.execTransactInternal(Binder.java:1045)
        at android.os.Binder.execTransact(Binder.java:1018)
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.content.ContentService.registerContentObserver(ContentService.java:344)
        at android.content.IContentService$Stub.onTransact(IContentService.java:482)
        at android.os.Binder.execTransactInternal(Binder.java:1045)
        at android.os.Binder.execTransact(Binder.java:1018)

从日志中可以看到mPendingTasks.size在一直增大,在SpellCheckerSession.java中

private int mState = STATE_WAIT_CONNECTION;
private static final class SpellCheckerSessionListenerImpl
            extends ISpellCheckerSessionListener.Stub {

    public void getSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords)
            processOrEnqueueTask
    }

    public void getSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
            processOrEnqueueTask
    }

    private void processOrEnqueueTask(SpellCheckerParams scp) {
        ...
        if (mState == STATE_WAIT_CONNECTION) {
            ...
            mPendingTasks.offer(scp);
            ...
            if (DBG) Log.d(TAG, "queueing tasks in processOrEnqueueTask since the"
                    + " connection is not established."
                    + " mPendingTasks.size()=" + mPendingTasks.size());
        }
        return;
        ...
    }


    public void onServiceConnected(ISpellCheckerSession session) {
        ...
        mState = STATE_CONNECTED;
        ...
        while (!mPendingTasks.isEmpty()) {
            processTask(session, mPendingTasks.poll(), false);
        }
        ...
    }

}

就是因为没有调用onServiceConnected导致mPendingTasks一直没有被消费,最后出现内存溢出,接下来就看是什么原因导致连接不上服务的。

还是在SpellCheckerSession.java

    private static final class InternalListener extends ITextServicesSessionListener.Stub {
        private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;

        public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
            mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
        }

        @Override
        public void onServiceConnected(ISpellCheckerSession session) {
            mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
        }
    }

    /**
     * @hide
     */
    public ITextServicesSessionListener getTextServicesSessionListener() {
        return mInternalListener;
    }

在TextServicesManager的newSpellCheckerSession里调用了getSpellCheckerService,然后在TextServicesManagerService调用bindGroup.getISpellCheckerSessionOrQueueLocked,此时就到了上面日志中的at android.service.textservice.SpellCheckerService$SpellCheckerServiceBinder.getISpellCheckerSession(SpellCheckerService.java:339)

接下来就容易了,跟着日志看到

packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java

AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) {
    mService = service;
    final ContentResolver cres = service.getContentResolver();

    mObserver = new ContentObserver(null) {
        @Override
        public void onChange(boolean self) {
            mSuggestionsCache.clearCache();
        }
    };
    cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
}

ContentService

    public void registerContentObserver(Uri uri, boolean notifyForDescendants,
            IContentObserver observer, int userHandle, int targetSdkVersion) {
        if (observer == null || uri == null) {
            throw new IllegalArgumentException("You must pass a valid uri and observer");
        }

        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();

        userHandle = handleIncomingUser(uri, pid, uid,
                Intent.FLAG_GRANT_READ_URI_PERMISSION, true, userHandle);

        final String msg = LocalServices.getService(ActivityManagerInternal.class)
                .checkContentProviderAccess(uri.getAuthority(), userHandle);
        if (msg != null) {
            if (targetSdkVersion >= Build.VERSION_CODES.O) {
                throw new SecurityException(msg);
            } else {
                if (msg.startsWith("Failed to find provider")) {
                    // Sigh, we need to quietly let apps targeting older API
                    // levels notify on non-existent providers.
                } else {
                    Log.w(TAG, "Ignoring content changes for " + uri + " from " + uid + ": " + msg);
                    return;
                }
            }
        }

        synchronized (mRootNode) {
            mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
                    uid, pid, userHandle);
            if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
                    " with notifyForDescendants " + notifyForDescendants);
        }
    }

可以看到在这里因为查找不到 "字典的内容提供者"?然后抛出了异常,经过定位原来是我们系统裁剪了UserDictionaryProvider导致的,将UserDictionaryProvider推到系统问题成功解决。

后记

1.newSpellCheckerSession还会判断系统语言,当我们设置成中文时,因为中文不在"拼写检查工具"支持的语言里,所以也不会造成内存溢出。

2.设置EditText的inputType为"textNoSuggestions",也不会造成内存溢出

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:22:54  更:2022-04-04 12:23:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 20:53:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码