Android パフォーマンスの最適化 スレッド ネイティブ レイヤーのソース コード分析 (InternalError/メモリ不足)

最近、Bugly で OOM 問題を扱いましたが、その多くはスレッドの作成および起動プロセス中に発生し、最終的には 32 ビット 4G 仮想メモリの不足が原因であると分析されましたが、それでも Java 層の分析が必要でしたスレッドのソース コード処理で、InternalError/メモリ不足の例外がスローされる場合があります。

スレッドエラースタック:
ここに画像の説明を挿入

Javaスレッドの作成からプロセスの起動まで:

Thread.start() から JniEnv を作成します -> C++ レイヤー CreateNativeThread() -> JNIEnvExt::Create() -> C++ レイヤー pthread_create() -> assign_thread() ヒープ メモリを割り当てます -> Linux レイヤー clone() 新しいスレッドをコピーします ->リフレクションは Thread.run() を呼び出します

ソースコード分析
Java レイヤー Thread#start():
ここに画像の説明を挿入

次に、C++ 層に進みます。

http://aospxref.com/android-7.1.2_r39/xref/art/runtime/native/java_lang_Thread.cc

/art/runtime/native/java_lang_Thread.cc

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,jboolean daemon) {
    
    
    //... 部分zygote进程是不允许创建线程,会抛出InternalError异常
    //接下来看
    Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}

http://aospxref.com/android-7.1.2_r39/xref/art/runtime/thread.cc

/art/ランタイム/スレッド.cc

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
    
    
    CHECK(java_peer != nullptr);
    Thread* self = static_cast<JNIEnvExt*>(env)->self;

       //若当虚拟机正在关闭时,创建线程会抛出InternalError异常
    Runtime* runtime = Runtime::Current();
    bool thread_start_during_shutdown = false;
    {
    
    
      MutexLock mu(self, *Locks::runtime_shutdown_lock_);
      if (runtime->IsShuttingDownLocked()) {
    
    
        thread_start_during_shutdown = true;
      } else {
    
    
        runtime->StartThreadBirth();
      }
    }
    if (thread_start_during_shutdown) {
    
    
      ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
      env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
      return;
    }
    Thread* child_thread = new Thread(is_daemon);//创建java层thread对应的c++对象
    child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer); // 将java层的Thread引用创建成全局引用
    stack_size = FixStackSize(stack_size);// 计算出线程的堆内存大小,默认计算出是1040kb
  
    //将线程记录在线程组中
    env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
                      reinterpret_cast<jlong>(child_thread));
  
       //给c++层Threa对象创建JNIEnvExt环境(一个线程对应一个jniEnv),这一步可能会OOM
    std::unique_ptr<JNIEnvExt> child_jni_env_ext(
        JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));
  
 
    int pthread_create_result = 0;
    if (child_jni_env_ext.get() != nullptr) {
    
    // 闯将线程的JniEnv成功时
      pthread_t new_pthread;
      pthread_attr_t attr;
      child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();//将JniEnv赋值给C++层Thread对象
      CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
      CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
                         "PTHREAD_CREATE_DETACHED");
      CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
         //真正创建线程,参数1是线程标识符;参数2:线程属性设置(设置堆的大小等等);参数3:线程函数的起始地址;参数4:传递给参数3线程函数的参数;
      pthread_create_result = pthread_create(&new_pthread,
                                             &attr,
                                             Thread::CreateCallback,
                                             child_thread);
      CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
  
      if (pthread_create_result == 0) {
    
     // 若是线程创建,执行完Java层Thread#run()后会返回0
        child_jni_env_ext.release();
        return; // 释放执行完成任务的线程资源,不会往下走
      }
    }
  
    //当创建失败时,释放资源
    env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer); //删除java层的thread 全局引用
    child_thread->tlsPtr_.jpeer = nullptr;
    delete child_thread; //删除 c++层Thread指针
    child_thread = nullptr;
    //从线程组中移除
    env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
	
	//当创建线程的JniEnv失败或者pthread_create创建线程失败时,会抛出异常
    {
    
    
      std::string msg(child_jni_env_ext.get() == nullptr ?
          "Could not allocate JNI Env" : //当线程创建JniEnv 环境失败时,抛出该提示语
          StringPrintf("pthread_create (%s stack) failed: %s",
                                   PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
      ScopedObjectAccess soa(env);
      soa.Self()->ThrowOutOfMemoryError(msg.c_str()); //抛出OOM 异常
    }
}

FixStackSize()スレッドのヒープメモリサイズを計算すると、ヒープメモリ=1024K(1M)+8k+8K=1040kとなります

static size_t FixStackSize(size_t stack_size) {
    
     //参数是java层中thread 的stack_size默认0
    if (stack_size == 0) {
    
    
         // GetDefaultStackSize 是启动art时命令行的 "-Xss=" 参数, Android 中没有该参数,因此为0.
      stack_size = Runtime::Current()->GetDefaultStackSize();
    }
    // bionic pthread 默认栈大小是 1M
    stack_size += 1 * MB;
    //...
    if (Runtime::Current()->ExplicitStackOverflowChecks()) {
    
    
       //8k
      stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
    } else {
    
    
      8k+8K
      stack_size += Thread::kStackOverflowImplicitCheckSize +
          GetStackOverflowReservedBytes(kRuntimeISA);
    }
    //...
    return stack_size;
  }

JniEnv の作成プロセスを表示します。
http://aospxref.com/android-7.1.2_r39/xref/art/runtime/jni_env_ext.cc

/art/runtime/jni_env_ext.cc

JNIEnvExt* JNIEnvExt::Create(Thread* self_in, JavaVMExt* vm_in) {
    
    
    std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in));
    if (CheckLocalsValid(ret.get())) {
    
    
      return ret.release();
    }
    return nullptr;
}

JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in)
        : self(self_in),
        vm(vm_in),
        local_ref_cookie(IRT_FIRST_SEGMENT),
        locals(kLocalsInitial, kLocalsMax, kLocal, false),
        check_jni(false),
        runtime_deleted(false),
        critical(0),
        monitors("monitors", kMonitorsInitial, kMonitorsMax) {
    
    
    functions = unchecked_functions = GetJniNativeInterface(); //获取到全局的Jni函数接口列表
    if (vm->IsCheckJniEnabled()) {
    
    
      SetCheckJniEnabled(true);
    }
}

pthread のスレッド作成プロセスを表示します。

http://aospxref.com/android-7.1.2_r39/xref/bionic/libc/bionic/pthread_create.cpp
/bionic/libc/bionic/pthread_create.cpp

int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                    void* (*start_routine)(void*), void* arg) {
    
    

    pthread_internal_t* thread = NULL;
    void* child_stack = NULL;
    //创建线程的堆内存
    int result = __allocate_thread(&thread_attr, &thread, &child_stack);
    if (result != 0) {
    
    
     return result; //若是创建失败,则抛出oom 异常
    }
    //....  
    int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
        CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
	//linux 的clone 进程,即
    int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));
    if (rc == -1) {
    
    
      int clone_errno = errno;
      if (thread->mmap_size != 0) {
    
    
	    //当拷贝失败时,释放申请好的匿名共享内存
        munmap(thread->attr.stack_base, thread->mmap_size);
      }
	  // 当拷贝进程失败时,会输出错误日志 clone faild
      __libc_format_log(ANDROID_LOG_WARN, "libc", "pthread_create failed: clone failed: %s", strerror(errno));
      return clone_errno;
    }
	//...
    return 0;
}

次に、__allocate_thread()スレッドのヒープメモリを作成する方法を見てみましょう

static int __allocate_thread(pthread_attr_t* attr, pthread_internal_t** threadp, void** child_stack) {
    
    
    size_t mmap_size;
    uint8_t* stack_top;
    if (attr->stack_base == NULL) {
    
    
      //计算出mmap_size
      mmap_size = BIONIC_ALIGN(attr->stack_size + sizeof(pthread_internal_t), PAGE_SIZE);
      attr->guard_size = BIONIC_ALIGN(attr->guard_size, PAGE_SIZE);
      attr->stack_base = __create_thread_mapped_space(mmap_size, attr->guard_size);
      if (attr->stack_base == NULL) {
    
    
        return EAGAIN; //创建mapp空间失败,则返回错误码
      }
      stack_top = reinterpret_cast<uint8_t*>(attr->stack_base) + mmap_size;
    }
    //....
    return 0;
}

スレッド割り当て mmap_size = スレッド ヒープ サイズ (1040k) + スレッド構造体 pthread_internal_t のサイズ、スレッド構造体 pthread_internal_t にはスレッド名、localtread などが含まれます。

次に見てみましょう__create_thread_mapped_space():

static void* __create_thread_mapped_space(size_t mmap_size, size_t stack_guard_size) {
    
    
    int prot = PROT_READ | PROT_WRITE;
    int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
    //根据MAP_ANONYMOUS flags,分配指定mmap_size大小的匿名共享内存
    void* space = mmap(NULL, mmap_size, prot, flags, -1, 0);
    if (space == MAP_FAILED) {
    
    
      __libc_format_log(ANDROID_LOG_WARN,
                        "libc","pthread_create failed: couldn't allocate %zu-bytes mapped space: %s", mmap_size, strerror(errno));
      return NULL;
    }
    //....
    return space;
}

これは Bugly に相当しますpthread_create failed: couldn't allocate 1085440-bytes mapped space: Out of memory。つまり、スレッド作成のヒープ メモリが失敗し、仮想メモリが不足しています。

次に、Linux がどのように新しい子プロセスを作成するか、つまりスレッドを作成するかを見てみましょう。

まず、Linux のいくつかの概念を理解しましょう

プロセスの作成:

  • Linux プロセスの作成: fork() を通じて、リソース (コード セグメント、データ セグメント、ヒープ、スタックを含む) を子プロセスにコピーしますが、2 つのプロセスのメモリ リソースは共有されません。
  • Linux ユーザーレベルのスレッド作成: pthread ライブラリの pthread_create() を通じてスレッドを作成し、同じプロセス内のリソースを共有します。
  • Linux カーネル スレッドの作成: kthread_create() による

Linux では、スレッドはプロセス間でリソースを共有する手段であり、スレッドはプロセスとリソースを共有するプロセスとみなすこともできます。スレッドとプロセスの違いは、リソースを共有するかどうかです。

http://aospxref.com/android-7.1.2_r39/xref/bionic/libc/bionic/clone.cpp
/bionic/libc/bionic/clone.cpp

int clone(int (*fn)(void*), void* child_stack, int flags, void* arg, ...) {
    
    
    //真正拷贝子进程过程,更多调用过程
   int clone_result = __bionic_clone(flags, child_stack, parent_tid, new_tls, child_tid, fn, arg);
   self->set_cached_pid(parent_pid);
   return clone_result;
}

pthread_create()->linux的clone()->sys_clone()->do_fork()->copy_process(), このプロセスでは、現在のプロセス (メインプロセスなど) のリソースがコピーされ、
プロセスが制限を超えていないか (つまり、スレッドが最大値を超えているかどうか)、fd が存在するかどうかがチェックされます。リソースが制限を超えています (Linux ではソケットとファイルが両方とも fd です)。共有シグナル処理。

詳細については、http://gityuan.com/2017/08/05/linux-process-fork/ をご覧ください。

最後に、各コードに対応する例外メッセージを確認します。
http://aospxref.com/android-7.1.2_r39/xref/bionic/libc/bionic/strerror.cpp#36

/bionic/libc/bionic/strerror.cpp


char* strerror(int error_number) {
    
    
    // Just return the original constant in the easy cases.
    char* result = const_cast<char*>(__strerror_lookup(error_number));
    if (result != nullptr) {
    
    
      return result;
    }
  
    result = g_strerror_tls_buffer.get();
    strerror_r(error_number, result, g_strerror_tls_buffer.size());
    return result;
}

http://aospxref.com/android-7.1.2_r39/xref/bionic/libc/include/sys/_errdefs.h
/bionic/libc/include/sys/_errdefs.h

__BIONIC_ERRDEF( EAGAIN         ,  11, "Try again" )
__BIONIC_ERRDEF( ENOMEM         ,  12, "Out of memory" )
__BIONIC_ERRDEF( EACCES         ,  13, "Permission denied" )
__BIONIC_ERRDEF( EMFILE         ,  24, "Too many open files" )

スレッド例外キャプチャ プロセッサの拡張ポイントは次のとおりです。

  • Java 層例外がキャッチされた場合、スレッドは作成できません。それ以外の場合はスレッドがスローされます InternalError:Thread starting during runtime shutdownつまり、例外報告用のスレッドを事前に作成しておく必要があります。

  • 例外が発生した場合、メモリが不足しているときに例外を報告し、OkHttp 送信 (新しいスレッドが作成されます) を使用します。これにより、新しい OOM 例外が発生する可能性があります。

情報参照先

  • http://gityuan.com/2016/09/24/android-thread/
  • https://blog.csdn.net/Tencent_Bugly/article/details/78542324

おすすめ

転載: blog.csdn.net/hexingen/article/details/131959505