华为手机6.0线程OOM分析

Link在华为手机上有一些很高的Crash,原因是RxJava调用不当导致的。

一.问题描述

Link有大量因为OOM引起的Crash,日志上总体表现为 pthread_create (1040KB stack) failed: Out of memory,集中发生在Android6.0及以上的华为手机

二.问题分析

2.1 代码分析

Android系统中,OutOfMemoryError这个错误是怎么被系统抛出的?基于Android6.0的代码可以看到:
Android虚拟机最终抛出OutOfMemoryError的代码位于 /art/runtime/thread.cc

void Thread::ThrowOutOfMemoryError(const char* msg)

参数msg携带了OOM时的错误信息。搜索代码可以看到异常在下面这个函数抛出:

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon)

同时会打出与我们问题一致的错误信息:

StringPrintf("pthread_create (%s stack) failed: %s", PrettySize(stack_size).c_str(), strerror(pthread_create_result)))

由此可以定位到问题 -- 创建线程时导致了OOM。那么,为什么华为手机会在创建线程时OOM呢?

2.2 推断

既然抛出OOM,一定是触发了系统线程限制,Android基于Linux,所以在 /proc/sys/kernel/threads-max 中有描述线程限制,可以通过命令cat /proc/sys/kernel/threads-max 查看。我们分别查看华为Mate7和小米Note的线程限制。

Mate7
shell@hwmt7:/ $ cat /proc/sys/kernel/threads-max
26599
MiNote
shell@virgo:/ $ cat /proc/sys/kernel/threads-max
39595

我们可以发现,华为在线程限制上非常严苛,Mate7的最大线程数远远小于小米Note,所以导致单华为机型爆发Crash。那么是哪里代码导致了线程爆发呢?
我们通过Fabric任务栈可以发现,这几个Crash爆发时,线程数量都在400+,而且有大量RxIoSchedule线程处于wait状态,可以推断出RxJava调度器Scheduler.io中维护的线程池没起作用

2.3 验证

我们先对上面的推断做一个本地实验:试图复现错误信息一致的OOM。

  • 过程:用Rxjava在IO调度器中创建大量线程模仿网络请求,看是否会回收。
  • 预期:当线程数超过/proc/sys/kernel/threads-max中规定的上限时产生OOM崩溃。
for循环执行测试代码:
  private void sendData(final int num) {
    Observable.create(new Observable.OnSubscribe() {
      @Override public void call(Subscriber subscriber) {
        try {
          Thread.sleep(400);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        subscriber.onNext(num);
        subscriber.onCompleted();
      }
    }).subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() {
      @Override public void onNext(Integer num) {
        Log.d(TAG, "current_tread == " + Thread.currentThread().getId());
      }

      @Override public void onCompleted() {
        Log.d(TAG, "work_num == " + num);
      }

      @Override public void onError(Throwable e) {
      }
    });
  }
  • 结论:当for循环超过600时,得到和上报日志完全相同的Crash,IO调度器的线程池失效了。为什么IO调度器的线程池会失效呢?

2.4 定位与解决

我们看Scheduler.io的原码发现,里面的线程池是一个可以自增、无上限的线程池,而且每个线程设置了一个60s的保活时间防止被结束。也就是说:在60s内做频繁操作时,io调度器线程池并没有约束线程数且会不断开新线程。我们搜索代码查到Dig打点库中上传数据时,有频繁密集请求网络,并使用IO了调度器,导致不断开启线程最终crash。

解决办法:在这种密集频繁的操作时,自己指定一个Executor作为调度器

2.5 监控措施

可以利用linux的inotify机制进行监控:

  • watch /proc/pid/task来监控线程使用情况。

你可能感兴趣的:(华为手机6.0线程OOM分析)