首先简述下Signal Catcher,Signal Catcher线程接受到kernel系统底层的消息进行dump当前虚拟机的信息并且设置每个线程的标志位(check_point)和请求线程状态为挂起,当线程运行过程中进行上下文切换时会检查该标记。等到线程都挂起后,开始遍历Dump每个线程的堆栈和线程数据后再唤醒线程。关于ANR的更多内容在我的其他博客中进行查阅~~.
本文重点讲的是在分析Singal Catcher时对线程有了更新的了解。
在Android里面只能通过pthread_create去创建一个线程,Thread只是Android Runtime里面的一个类,一个Thread对象创建之后就会被保存在线程的TLS区域,所以一个Linux线程都对应了一个Thread对象,可以通过Thread的Current()函数来获取当前线程关联的Thread对象,通过这个Thread对象就可以获取一些重要信息,例如当前线程的Java线程状态,Java栈帧,JNI函数指针列表等等,之所以说是Java线程状态,Java栈帧,是因为Android运行时其实是没有自己单独的线程机制的,Java线程底层都是一个Linux线程,但是Linux线程是没有像Watting,Blocked等状态的,并且Linux线程也是没有Java堆栈的,那么这些线程状态和Java栈帧必须有一个地方保存,要不然就丢失了,Thread对象就是一个很理想的“储物柜”。
只有当创建出来的Thread对象执行了attach函数后,一个Linux线程在真正和虚拟机运行时关联起来,才变成了Java线程,才有了自己的java线程状态和java栈帧等数据结构,那些纯粹的native是不能执行java代码的,所以当系统发生crash或者anr进行dump进程的堆栈的时候,有些线程是没有java堆栈的,只有native和kernel堆栈,就是这个原因。
那么这个attach()函数中做了哪些事情呢:
Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group,bool create_peer) { Runtime* runtime = Runtime::Current(); ...... Thread* self; { MutexLock mu(nullptr, *Locks::runtime_shutdown_lock_); if (runtime->IsShuttingDownLocked()) { ...... } else { Runtime::Current()->StartThreadBirth(); self = new Thread(as_daemon); //新建一个Thread对象 bool init_success = self->Init(runtime->GetThreadList(), runtime->GetJavaVM()); //调用init函数 Runtime::Current()->EndThreadBirth(); if (!init_success) { delete self; return nullptr; } } } ...... self->InitStringEntryPoints(); CHECK_NE(self->GetState(), kRunnable); self->SetState(kNative); ...... return self; }
首先创建了一个Thread对象,接着执行了init()函数,然后在最后修改了线程的状态kNative(Java线程的状态是保存在Thread对象中的,具体来说是由对象中的tls32_这个结构体保存的,可以通过修改这个结构体来设置线程当前的状态:
inline ThreadState Thread::SetState(ThreadState new_state) { // Cannot use this code to change into Runnable as changing to Runnable should fail if // old_state_and_flags.suspend_request is true. DCHECK_NE(new_state, kRunnable); if (kIsDebugBuild && this != Thread::Current()) { std::string name; GetThreadName(name); LOG(FATAL) << "Thread \"" << name << "\"(" << this << " != Thread::Current()=" << Thread::Current() << ") changing state to " << new_state; } union StateAndFlags old_state_and_flags; old_state_and_flags.as_int = tls32_.state_and_flags.as_int; tls32_.state_and_flags.as_struct.state = new_state; return static_cast<ThreadState>(old_state_and_flags.as_struct.state); }
enum ThreadState { // Thread.State JDWP state kTerminated = 66, // TERMINATED TS_ZOMBIE Thread.run has returned, but Thread* still around kRunnable, // RUNNABLE TS_RUNNING runnable kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() kBlocked, // BLOCKED TS_MONITOR blocked on a monitor kWaiting, // WAITING TS_WAIT in Object.wait() kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run kWaitingPerformingGc, // WAITING TS_WAIT performing GC kWaitingForDebuggerSend, // WAITING TS_WAIT blocked waiting for events to be sent kWaitingForDebuggerToAttach, // WAITING TS_WAIT blocked waiting for debugger to attach kWaitingInMainDebuggerLoop, // WAITING TS_WAIT blocking/reading/processing debugger events kWaitingForDebuggerSuspension, // WAITING TS_WAIT waiting for debugger suspend all kWaitingForJniOnLoad, // WAITING TS_WAIT waiting for execution of dlopen and JNI on load code kWaitingForSignalCatcherOutput, // WAITING TS_WAIT waiting for signal catcher IO to complete kWaitingInMainSignalCatcherLoop, // WAITING TS_WAIT blocking/reading/processing signals kWaitingForDeoptimization, // WAITING TS_WAIT waiting for deoptimization suspend all kWaitingForMethodTracingStart, // WAITING TS_WAIT waiting for method tracing to start kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code kNative, // RUNNABLE TS_RUNNING running in a JNI native method kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger };
这里主要分析init()函数,首先要先了解一下ART执行代码的方式,ART虚拟机和Dalvik最大的变化就是ART虚拟机不在解释执行字节码,而是直接找到对应的机器码直接执行。ART会在安装应用程序的时候执行dex2oat进程得到一个oat文件完成字节码翻译成本地机器码的工作,这个oat文件一般保存在/data/app/应用名称/oat/目录下,这个oat文件里面就是编译好的机器码,但是这些机器码不可能单独存在,需要借助于ART运行时(执行一个jni方法或者在heap中操作),这个可以类比于编译so库文件的时候引用到了外部函数(其实oat和so文件都是ELF可执行格式文件,只是oat文件相比于标准的ELF格式文件多出了几个section)。区别是打开标准的so文件的时候,一般用的是dlopen这个函数,该函数会把没有加载的so库加载进来,然后把这些外部函数重定位好;而oat文件为了快速加载,ART在线程的TLS区域保存了一些函数,编译好的机器码就是调用这些函数指针来和AT运行时建立联系,这些函数就是在Thread的init过程中初始化好的