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

近期处理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),这一步可能会OOMstd::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()后会返回0child_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默认0if (stack_size == 0) {// GetDefaultStackSize 是启动art时命令行的 "-Xss=" 参数, Android 中没有该参数,因此为0.stack_size = Runtime::Current()->GetDefaultStackSize();}// bionic pthread 默认栈大小是 1Mstack_size += 1 * MB;//...if (Runtime::Current()->ExplicitStackOverflowChecks()) {//8kstack_size += GetStackOverflowReservedBytes(kRuntimeISA);} else {8k+8Kstack_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_sizemmap_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.mzph.cn/news/13187.shtml

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

相关文章

无涯教程-jQuery - serialize( )方法函数

serialize()方法将一组输入元素序列化为数据字符串。 serialize( ) - 语法 $.serialize( ) serialize( ) - 示例 假设无涯教程在serialize.php文件中具有以下PHP内容- <?php if( $_REQUEST["name"] ) {$name$_REQUEST[name];echo "Welcome ". $na…

递归:一个图教学会递归原理

递归的特点 实际上&#xff0c;递归有两个显著的特征,终止条件和自身调用: 自身调用&#xff1a;原问题可以分解为子问题&#xff0c;子问题和原问题的求解方法是一致的&#xff0c;即都是调用自身的同一个函数。终止条件&#xff1a;递归必须有一个终止的条件&#xff0c;即不…

被泼冷水后,谁能超越微服务?

历史总会重演。一切刚过去的&#xff0c;又会被重新提起。开源项目Codename One的联合创始人Shai&#xff0c;曾是Sun Microsystems开源LWUIT项目的共同作者&#xff0c;参与了无数开源项目。作为最早一批Java开发者&#xff0c;最近感慨道&#xff1a;单体&#xff0c;又回来了…

【matlab】机器人工具箱快速上手-动力学仿真(代码直接复制可用)

动力学代码&#xff0c;按需修改参数 各关节力矩-关节变量的关系曲线&#xff1a; %%%%%%%%SCARA机器人仿真模型 l[0.457 0.325]; L(1) Link(d,0,a,l(1),alpha,0,standard,qlim,[-130 130]*pi/180);%连杆1 L(2)Link(d,0,a,l(2),alpha,pi,standard,qlim,[-145 145]*pi/180);%连…

Intel RealSense D455(D400系列) Linux-ROS 安装配置(亲测可用)

硬件&#xff1a;Intel RealSense D455 系统&#xff1a;Ubuntu 18.04 Part_1: 安装librealsense SDK2.0 1.1 注册密钥 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE或者 sudo apt-key adv --keyserver hkp:/…

8.python设计模式【组合模式】

内容&#xff1a;将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。角色&#xff1a; 抽象组建&#xff08;component&#xff09;叶子组建(Leaf)复合组建(Composite)客户端 (Client) UML 图 举个例子 需求&#xf…

【初阶C语言】整数比大小

各位大佬的光临已是上上签 在C语言刷题过程中&#xff0c;一定遇到过很多比大小的题目&#xff0c;那么本节就专门介绍比大小的方法&#xff0c;若大佬们还有更优解&#xff0c;欢迎补充呀&#xff01; 本节讲解的方法主要有三种&#xff1a;1.条件判断 2.三目操作符 3.函数调…

【Linux命令200例】lsattr用于查看文件或目录的属性

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;Linux命令大全。 &#x1f3c6;本专栏我们会通过具体的系统的命令讲解加上鲜活的实操案例对各个命令进行深入…

Windows下Nginx安装与配置教程

一、前言 1、Nginx是什么&#xff1f; Nginx是一个开源的Web服务器&#xff0c;同时Nginx也提供了反向代理和负载均衡的功能。 Nginx通常作为负载均衡器暴露在外网接受用户请求&#xff0c;同时也使用其反向代理的功能&#xff0c;将用户的请求转发到实际提供服务的内网服务器…

Power BI-云端报表定时刷新--ODBC、MySQL、Oracle等其他本地数据源的刷新(二)

ODBC数据源 一些小众的数据源无法直接连接&#xff0c;需要通过微软系统自带的应用“ODBC数据源”连接。 1.首次使用应安装对应数据库的ODBC驱动程序&#xff0c;Mysql的ODBC驱动需要手动安装 2.在web服务中进行数据源的配置 Mysql数据源 1.Powerbi与Gateway第一次连SQL…

Java工程师研学之路【002Java基础语法上】

知识体系&#xff08;Knowledge system&#xff09; 练习&#xff08;practice&#xff09; 要求&#xff1a;从控制台输入两个数字&#xff0c;然后输出两个数字的求和结果。 import java.util.Scanner; public class HelloJava {public static void sum(){System.out.print…

maven编译报错

参考链接&#xff1a;mvn打包No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK_51CTO博客_mvn打包命令 在执行 yum install -y java-1.8.0-opensdk命令后&#xff0c;使用maven去编译打包&#xff0c;结果报错&#xff0c; …

【Leetcode】62.不同路径

一、题目 1、题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径? 示例1: 输入:m = 3, n = 7 输出:…

《向量数据库指南》——使用Milvus Cloud操作员安装Milvus Cloud独立版

Milvus cloud操作员HelmDocker Compose Milvus cloud Operator是一种解决方案,帮助您在目标Kubernetes(K8s)集群上部署和管理完整的Milvus cloud服务堆栈。该堆栈包含所有Milvus cloud组件和相关依赖项,如etcd、Pulsar和MinIO。本主题介绍如何使用Milvus cloud Operator安…

文心一言 VS 讯飞星火 VS chatgpt (67)-- 算法导论6.5 6题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;67&#xff09;-- 算法导论6.5 6题 六、在 HEAP-INCREASE-KEY 的第 5 行的交换操作中&#xff0c;一般需要通过三次赋值来完成。想一想如何利用INSERTION-SORT 内循环部分的思想&#xff0c;只用一次赋值就完成这一交换操作? 文…

STUN工作原理

目录 一. 前言 二. STUN报文格式 STUN Header RFC3489 RFC5389 STUN Message Body RFC3489 RFC5389 三. WebRTC对STUN协议的支持 四. STUN工作流程 1. 使用STUN获取NAT映射后的地址 五. 参考资料 一. 前言 现实网络环境中绝大多数主机都是处于 NAT 之后&#xff0c…

Docker 容器高级操作

Docker容器高级操作 Docker容器创建、停止、启动、删除等基础操作上篇已述,然Docker容器被广大开发者青睐,不可能只有如此简单的功能,必有高阶功法。那么接下来 让我们一同走进容器操作的高级篇,领略其高级操作的魅力。 查看容器 docker ps -a | grep tomcat [root@tudou…

Java中的代理模式

Java中的代理模式 1. 静态代理JDK动态代理CGLib动态代理 1. 静态代理 接口 public interface ICeo {void meeting(String name) throws InterruptedException; }目标类 public class Ceo implements ICeo{public void meeting(String name) throws InterruptedException {Th…

LearnOpenGL_Day1

文章目录 前期准备下载GLFW下载GLAD 引入库文件生成窗口重要概念——双缓冲 &#xff08;double buffer&#xff09;代码实现 学习总结 前期准备 下载GLFW GLFW DOWNLOAD 解压后使用CMake编译至新创建的bulid文件夹下&#xff1a; 下载GLAD 引入库文件 创建好工程&#x…

01 矩阵(力扣)多源广度优先搜索 JAVA

给定一个由 0 和 1 组成的矩阵 mat &#xff0c;请输出一个大小相同的矩阵&#xff0c;其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 输入&#xff1a;mat [[0,0,0],[0,1,0],[0,0,0]] 输出&#xff1a;[[0,0,0],[0,1,0],[0,0,0]] 输入…