《Android 创建线程源码与OOM分析》

本文深入探讨了Android中线程创建的过程,从Java到Native,再到ART和Linux内核调用,分析了OOM异常的原因。文章指出,线程创建过程中发生OOM主要是由于虚拟内存地址空间耗尽。堆栈A的OOM是由于pthread_create分配栈内存失败,堆栈B则是由于JNIEnvExt::Create调用失败。此外,文章还提供了如何判断是虚拟内存耗尽还是FileDescriptor超出上限的方法。
摘要由CSDN通过智能技术生成

本文来自于腾讯Bugly公众号(weixinBugly), 作者:taylorcyang,未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/Z7cCCF8jzS6NpVEd0b0Emg

| 导语 企鹅FM近几个版本的外网Crash出现很多OutOfMemory(以下简称OOM)问题,Crash的堆栈都在Thread::start方法上。该文详细分析了发生原因。

前两天刚好在微信公众号看到有文章 《不可思议的OOM》 也是分析相同的问题,文中提到
FileDescriptor达到上限也可能引发相同的OOM。笔者仔细阅读源码,并验证确实成立!
在原文第5节之后,追加一节,分析上述场景。

有两种栈:

出现次数最多的一种,称之为 堆栈A

    java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memory
    java.lang.Thread.nativeCreate(Native Method)
    java.lang.Thread.start(Thread.java:745)
    ...

另一种,出现次数较少,称之为 堆栈B

java.lang.OutOfMemoryError: Could not allocate JNI Env
java.lang.Thread.nativeCreate(Native Method)
java.lang.Thread.start(Thread.java:729)
...

针对上面两种crash,分析一下Android/Linux中线程的创建过程,以及该OOM出现的原因。

PS: 文章很长,可以直接看结论。

1. 从java到native

我们看到最靠近栈顶的java方法调用的 Thread::start, 该方法内部调用了 native 方法 Thread::nativeCreate。如下:

public synchronized void start() {
    ...
    nativeCreate(this, stackSize, daemon);
    ...
}
这里我们主要关注传入的两个参数

1.this: 即Thread对象自身

2.stackSize: 这个比较关键,指定了新创建的线程的栈大小,单位是字节(Byte)

  • Thread 类其中一个构造函数,接受stackSize参数
  • 设置为0表示忽略之
  • 文档提到:提高stackSize会减少StackOverFlow的发生,而降低stackSize会减少OutOfMemory的发生
  • 另外:该参数是平台相关的,在一些平台上可能会直接被无视(有点类似Syste::gc的描述,然而目前来看gc在绝大多数平台都生效)

3.daemon: 表明新创建的线程是否是Daemon线程

2. 从native到ART

native层的代码分析的是Android 8.0的ART虚拟机源码,相关文件会给出全路径。

首先我们看一下 Thread::nativeCreate 的native实现。在art/runtime/native/java_lang_thread.cc 中。其主要逻辑会调用到 art/runtime/thread.cc 的 art::Thread::CreateNativeThread 函数来。

其主要逻辑如下:

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  // 代码1
  Thread* child_thread = new Thread(is_daemon);

  // 代码2
  std::unique_ptr<JNIEnvExt> child_jni_env_ext(
      JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM(), &error_msg));

  if (child_jni_env_ext.get() != nullptr) {
    // 代码片段3
    if (success) return;
  }

  // Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
  // 代码片段4
}

代码1

创建了 java.lang.Thread 相对应的 native 层C++对象。

代码2

有JNI基础的同学知道,java中每一个 java线程 对应一个 JniEnv 结构。这里的JniEnvExt 就是ART 中的 JniEnv。这里源码中有一段注释

Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and do not have a good way to report this on the child’s side.

代码片段4

3是创建线程的主要逻辑,4是执行创建流程失败的收尾逻辑。我们先跳过3,看一下4的逻辑。

std::string msg(child_jni_env_ext.get() == nullptr ?
  StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
  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异常的地方了。而且msg刚好和我们遇到的两种堆栈吻合。

  • child_jni_env_ext.get() == nullptr 对应的是堆栈B</
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值