Android性能优化之Thread native层源码分析(InternalError/Out of memory)

news2024/11/15 21:30:27

近期处理Bugly上OOM问题,很多发生在Thread创建启动过程,虽然最后分析出是32位4G虚拟内存不足导致,但还是分析下Java层Thread 源码过程,可能会抛出的异常InternalError/Out of memory。

Thread报错堆栈:
在这里插入图片描述

Java线程创建到启动过程

从Thread.start()-> c++层CreateNativeThread()->JNIEnvExt::Create()创建JniEnv ->c++层pthread_create()—> allocate_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/runtime/thread.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(),复制资源(包含代码段、数据段、堆、栈)给子进程,但两进程内存资源不共享;
  • Linux用户级别线程创建:通过pthread库中的pthread_create()创建线程,共享同个进程中的资源;
  • inux内核线程创建: 通过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 中socket、file都是fd),共享信号处理。

更多请阅读,http://gityuan.com/2017/08/05/linux-process-fork/

最后看下每个code对应的异常msg:
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" )

这里延伸点,Thread 异常捕捉处理器中:

  • 捕获到java 层异常时,不能再创建Thread,不然会抛出 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/797087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库|手把手教你成为 TiDB 的 Contributor

一、背景 最近笔者在 AskTUG 回答问题的时候发现&#xff0c;在 6.5.0 版本出现了几个显示未启动必要组件 NgMonitoring 的问题贴。经过排查发现&#xff0c;是 ngmonitoring.toml 中的配置文件出现了问题。文件中的 endpoints 应该是以逗号分隔的&#xff0c;但是却写成了以空…

JavaWeb 项目实现(二) 注销功能

3.注销功能 接前篇&#xff0c;实现了登录功能之后&#xff0c;现在实现注销功能。 因为我们实现登录就是在Session中记录了用户信息。 所以注销功能&#xff0c;就是在Session中移除用户信息。 代码&#xff1a;删除Session中的用户信息&#xff0c;跳转登录页面 package…

【安全渗透】第一次作业(编码知识总结)

目录 1. ASCII编码 2、Unicode 3、UTF-8 1. ASCII编码 ASCII 是“American Standard Code for Information Interchange”的缩写&#xff0c;翻译过来是“美国信息交换标准代码”。ASCII 的标准版本于 1967 年第一次发布&#xff0c;最后一次更新则是在 1986 年&#xff0c…

QEMU源码全解析13 —— QOM介绍(2)

接前一篇文章&#xff1a;QEMU源码全解析12 —— QOM介绍&#xff08;1&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 本回开始对QOM…

django学习笔记(1)

django创建项目 先创建一个文件夹用来放django的项目&#xff0c;我这里是My_Django_it 之后打开到该文件下&#xff0c;并用下面的指令来创建myDjango1项目 D:\>cd My_Django_itD:\My_Django_it>"D:\zzu_it\Django_learn\Scripts\django-admin.exe" startpr…

记录每日LeetCode 2500.删除每行中的最大值 Java实现

题目描述&#xff1a; 给你一个 m x n 大小的矩阵 grid &#xff0c;由若干正整数组成。 执行下述操作&#xff0c;直到 grid 变为空矩阵&#xff1a; 从每一行删除值最大的元素。如果存在多个这样的值&#xff0c;删除其中任何一个。 将删除元素中的最大值与答案相加。 …

Reinforcement Learning with Code 【Chapter 7. Temporal-Difference Learning】

Reinforcement Learning with Code This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation of Reinforcement Learning, . 文章…

Linux内核与内核空间是什么关系呢?

对内核空间的认识清晰了许多。要理解用户空间与内核空间需要有如下的几个认识&#xff1a; 内核的认识&#xff1a;从2个不同的角度来理解&#xff0c;一个是静态的角度&#xff0c;如“芦中人”所比喻&#xff0c;内核可以看做是一个lib库&#xff0c;内核对外提供的API打包…

快速远程桌面控制公司电脑远程办公

文章目录 快速远程桌面控制公司电脑远程办公**第一步****第二步****第三步** 快速远程桌面控制公司电脑远程办公 远程办公的概念很早就被提出来&#xff0c;但似乎并没有多少项目普及落实到实际应用层面&#xff0c;至少在前几年&#xff0c;远程办公距离我们仍然很遥远。但20…

1分钟上手Apifox

1、客户端右上角账号设置-生成令牌 2、IDEA下载插件 Apifox Helper 3、 配置ApiFoxHelper 令牌 4、在controller类界面右键 5、输入项目id 6、项目ID从客户端 项目设置-项目ID获取 7、导入成功 8、右键刷新查看导入的接口 9、自动生成数据&#xff08;某postman还要自己手输&a…

说一说java中的自定义注解之设计及实现

一、需求背景 比如我们需要对系统的部分接口进行token验证&#xff0c;防止对外的接口裸奔。所以&#xff0c;在调用这类接口前&#xff0c;先校验token的合法性&#xff0c;进而得到登录用户的userId/role/authority/tenantId等信息&#xff1b;再进一步对比当前用户是否有权…

MyBatis 快速入门【中】

&#x1f600;前言 本篇博文是MyBatis(简化数据库操作的持久层框架)–快速入门[上]的核心部分&#xff0c;分享了MyBatis实现sql的xml配置和一些关联配置、异常分析 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&a…

软件外包开发的需求分析

需求分析是软件开发中的关键步骤&#xff0c;其目的是确定用户需要什么样的软件&#xff0c;以及软件应该完成哪些任务。需求分析是软件工程的早期工作&#xff0c;也是软件项目成功的基础&#xff0c;因此花费大量精力和时间去做好需求分析是值得的。今天和大家分享软件需求分…

数字孪生-数字城市效果实现方法

数字孪生-数字城市效果实现方法 效果图&#xff1a; 一、效果分析&#xff1a; .0 1、城市非主展示区域白模快速生成方案&#xff1a; 参考视频&#xff1a; 1、CityEngine 引用数据源生成。 cityengine2022一键生成城市模型&#xff0c;不用再用blendergis_哔哩哔哩_bil…

Python运算符列表及其优先顺序、结合性

本文表格对Python中运算符的优先顺序进行了总结&#xff0c;从最高优先级&#xff08;最先绑定&#xff09;到最低优先级&#xff08;最后绑定&#xff09;。相同单元格内的运算符具有相同优先级。除非句法显式地给出&#xff0c;否则运算符均指二元运算。 相同单元格内的运算…

数据安全之全景图系列——数据分类分级落地实践

1、数据分类分级现状 我们正处于一个数据爆炸式增长的时代&#xff0c;随着产业数字化转型升级的推进&#xff0c;数据已被国家层面纳入生产要素&#xff0c;并且成为企业、社会和国家层面重要的战略资源。数据分类分级管理不仅是加强数据交换共享、提升数据资源价值的前提条件…

Unreal MorphTarget Connect Bone MetaData Curve功能学习

MorphTarget Connected Bone和MetaData Curve是两个较冷门功能&#xff0c;近期在制作一些功能时留意到这2个内容&#xff0c;故研究一下。 1.MorphTarget Connected Bone 在骨架编辑面板中&#xff0c;选中MorphTarget时&#xff0c;可找到Connected Bone选项&#xff1a; …

[数据库]对数据库事务进行总结

文章目录 1、什么是事务2、事务的特性&#xff08;ACID&#xff09;3、并发事务带来的问题4、四个隔离级别&#xff1a; 1、什么是事务 事务是逻辑上的一组操作&#xff0c;要么都执行&#xff0c;要么都不执行。 事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红…

图解SQL基础知识,小白也能看懂的SQL文章

本文介绍关系数据库的设计思想&#xff1a; 在 SQL 中&#xff0c;一切皆关系。 在计算机龄域有许多伟大的设计理念和思想&#xff0c;例如&#xff1a; 在 Unix 中&#xff0c;一切皆文件。 在面向对象的编程语言中&#xff0c;一切皆对象。 关系数据库同样也有自己的设计…

mybatis_使用注解开发

第一步&#xff1a;使用注解写一个接口 Select("select * from user")List<User> getUsers(); 第二步&#xff1a;绑定接口 第三步&#xff1a;测试 官方提示&#xff1a; 使用注解来映射简单语句会使代码显得更加简洁&#xff0c;但对于稍微复杂一点的语句&…