一般在Android开发中,分析线上问题的时候,最头大的就是OOM这类问题了。这里列举出几种和Thread线程相关的Crash日志来作为对这个问题的答复。
一般和线程相关的OOM比较常见的有如下两种:
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memory
java.lang.Thread.nativeCreate(Native Method)
java.lang.Thread.start(Thread.java:745)
java.lang.OutOfMemoryError: Could not allocate JNI Env
java.lang.Thread.nativeCreate(Native Method)
java.lang.Thread.start(Thread.java:729)
从第一个Crash日志中我们就可以得出这么一个结论: 创建线程的大小为1040KB 那到底怎么验证到底是不是1040KB呢?还是得要从系统源码来看。
从Java到Native层
栈顶的Java方法调用的 Thread::start, 该方法内部调用了 native 方法 Thread::nativeCreate
[-> Thread.java]
public synchronized void start() {
checkNotStarted(); // 保证线程只有启动一次
hasBeenStarted = true;
// this 表示线程本身
// stackSize 表示该参数是平台相关的,也可以自己设置
// daemon 表示线程是否是Daemon线程
nativeCreate(this, stackSize, daemon);
}
[-> java_lang_Thread.cc]
NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V")
[-> java_lang_Object.cc]
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
[-> java_lang_Thread.cc]
static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size, jboolean daemon) {
Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}
[-> thread.cc]
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
Thread* self = static_cast<JNIEnvExt*>(env)->self;
Runtime* runtime = Runtime::Current();
...
// 创建了 java.lang.Thread 相对应的 native 层C++对象
Thread* child_thread = new Thread(is_daemon);
child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
stack_size = FixStackSize(stack_size);
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
reinterpret_cast<jlong>(child_thread));
// java中每一个 java线程 对应一个 JniEnv 结构。这里的JniEnvExt 就是ART 中的 JniEnv
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) {
pthread_t new_pthread;
pthread_attr_t attr;
child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
// 创建线程
pthread_create_result = pthread_create(&new_pthread,
&attr, Thread::CreateCallback, child_thread);
if (pthread_create_result == 0) {
child_jni_env_ext.release();
return;
}
}
// Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
...
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
{
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());
}
...
}
[ -> pthread_create.cpp]
int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr, void* (*start_routine)(void*), void* arg) {
...
// 1. 分配栈。
pthread_internal_t* thread = NULL;
void* child_stack = NULL;
int result = __allocate_thread(&thread_attr, &thread, &child_stack);
if (result != 0) {
return result;
}
...
// 2. linux 系统调用 clone,执行真正的创建动作。
int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));
if (rc == -1) {
return errno;
}
...
return 0;
}
static int __allocate_thread(...) {
mmap_size = BIONIC_ALIGN(attr->stack_size + sizeof(pthread_internal_t), PAGE_SIZE);
attr->stack_base = __create_thread_mapped_space(mmap_size, attr->guard_size);
if (attr->stack_base == NULL) {
return EAGAIN;
}
...
}
static void* __create_thread_mapped_space(size_t mmap_size, size_t stack_guard_size) {
// Create a new private anonymous map.
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
void* space = mmap(NULL, mmap_size, prot, flags, -1, 0);
if (space == MAP_FAILED) {
...
return NULL;
}
}
主体逻辑再简单不过,即:调用mmap分配栈内存。这里mmap flag中指定了 MAP_ANONYMOUS,即 匿名内存映射(mapping anonymous) 。这是在Linux中分配大块内存的常用方式。其分配的是虚拟内存,对应页的物理内存并不会立即分配,而是在用到的时候,触发内核的缺页中断,然后中断处理函数再分配物理内存。
接下来重点看看这里:stack_size = FixStackSize(stack_size), 设置线程栈大小。
static size_t FixStackSize(size_t stack_size) {
// A stack size of zero means "use the default".
if (stack_size == 0) { //这里java层传递过来的是0
//GetDefaultStackSize是启动art时命令行的"-Xss="参数,实际运行环境没有传递,默认是0
stack_size = Runtime::Current()->GetDefaultStackSize();
}
// Dalvik used the bionic pthread default stack size for native threads,
// so include that here to support apps that expect large native stacks.
stack_size += 1 * MB;//默认栈大小是 1M
// Under sanitization, frames of the interpreter may become bigger, both for C code as
// well as the ShadowFrame. Ensure a larger minimum size. Otherwise initialization
// of all core classes cannot be done in all test circumstances.
if (kMemoryToolIsAvailable) {
stack_size = std::max(2 * MB, stack_size);
}
// It's not possible to request a stack smaller than the system-defined PTHREAD_STACK_MIN.
if (stack_size < PTHREAD_STACK_MIN) {
stack_size = PTHREAD_STACK_MIN;
}
if (Runtime::Current()->ExplicitStackOverflowChecks()) {
// It's likely that callers are trying to ensure they have at least a certain amount of
// stack space, so we should add our reserved space on top of what they requested, rather
// than implicitly take it away from them.
stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
} else {
// If we are going to use implicit stack checks, allocate space for the protected
// region at the bottom of the stack.
//stack_size += 8K + 8K;
stack_size += Thread::kStackOverflowImplicitCheckSize +
GetStackOverflowReservedBytes(kRuntimeISA);
}
// Some systems require the stack size to be a multiple of the system page size, so round up.
stack_size = RoundUp(stack_size, kPageSize);
return stack_size;
}
该函数主要对stack_size进行了设置,默认的栈大小中,包含了默认栈大小为1M,以及栈溢出相应检查所需空间8k + 8k。
所以,线程栈所需内存总大小 = 1M + 8k + 8k,即为1040k。
总结
- Android中,线程创建过程包括Java层和native层2个部分。
- Java层,Thread对象创建出来后,只是创建了一个Java对象而已,系统并没有真正的创建一个线程。
- Thread创建时,可以传递线程名参数,如果没有,则默认线程名为:“Thread-” + nextThreadNum()。为特定的线程命名是一个好习惯。
- 当Java层的Thead对象调用start()方法时,会调用native方法nativeCreate通过native层进行真正的线程创建过程。
- native层会通过pthread_create()函数调用内核创建线程。pthread_create是pthread库中的函数,通过syscall再调用到clone来创建线程。
- native层通过FixStackSize设置线程栈大小,默认情况下,线程栈所需内存总大小 = 1M + 8k + 8k,即为1040k。
- 如果在创建线程过程中,内存不足,会引发OOM。