背景
经常在分析问题或者看trace文件时遇到 jit thread 和Profile saver thread,本文基于Android S ART源码,介绍下我们常见的jit 线程和Profile Saver线程的工作流程。
创建
这两个线程都是在runtime初始化的时候创建的,属于应用“先天自带”
- "Jit thread pool worker thread 0" 线程是在CreateJit方法中创建的,前置条件dalvik.vm.usejit、dalvik.vm.usejitprofiles这两个属性有一个以上为true
- "Profile Saver" 线程在StartPfofileSaver函数中创建,前置条件属性dalvik.vm.usejitprofiles为true
我们直接看时序图,这其中涉及到各种各样的关于ART的参数,我们用到的时候再进行分析。
Jit 线程
jit 线程的创建最终是在thread_pool.cc中实现的,线程的名字是拼接出来的, "Jit thread pool" + "worker thread" + count
thread_pool_.reset(new ThreadPool("Jit thread pool", 1, kJitPoolNeedsPeers));
while (GetThreadCount() < max_active_workers_) {
const std::string worker_name = StringPrintf("%s worker thread %zu", name_.c_str(),
GetThreadCount());
threads_.push_back(
new ThreadPoolWorker(this, worker_name, worker_stack_size_));
}
通过pthread_create创建线程,创建之后线程的入口函数是Callback,从下面的trace中也能印证:
"Jit thread pool worker thread 0" daemon prio=5 tid=7 Native
| group="system" sCount=1 ucsCount=0 flags=1 obj=0x13200328 self=0x7fb077838f00
| sysTid=2074 nice=9 cgrp=top-app sched=0/0 handle=0x7faea2411cf0
| state=S schedstat=( 3536019 18589160 20 ) utm=0 stm=0 core=0 HZ=100
| stack=0x7faea2313000-0x7faea2315000 stackSize=1019KB
| held mutexes=
native: #00 pc 000000000005adf6 /apex/com.android.runtime/lib64/bionic/libc.so (syscall+22)
native: #01 pc 000000000042599e /apex/com.android.art/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+110)
native: #02 pc 00000000008a4137 /apex/com.android.art/lib64/libart.so (art::ThreadPool::GetTask(art::Thread*)+103)
native: #03 pc 00000000008a3451 /apex/com.android.art/lib64/libart.so (art::ThreadPoolWorker::Run()+113)
native: #04 pc 00000000008a2f48 /apex/com.android.art/lib64/libart.so (art::ThreadPoolWorker::Callback(void*)+264)
native: #05 pc 00000000000c758a /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+58)
native: #06 pc 000000000005fd87 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+55)
(no managed stack frames)
Callback函数调用worker的run方法,不断获取task并执行,
void ThreadPoolWorker::Run() {
Thread* self = Thread::Current();
Task* task = nullptr;
thread_pool_->creation_barier_.Pass(self);
while ((task = thread_pool_->GetTask(self)) != nullptr) {
task->Run(self);
task->Finalize();
}
}
我们关注下AddTask的调用地方:
调用点 | 调用代码 |
---|---|
在CreateThreadPool时如果当前是zygote会调用AddTask,用来对boot classpath jars进行verifyClass动作 | thread_pool_->AddTask(Thread::Current(), new ZygoteVerificationTask()); |
zygote如果bootimage有profile文件,会调用AddTask进行“compile all methods in that profile” | thread_pool_->AddTask(Thread::Current(), new ZygoteTask()); |
OpenDexFilesFromOat方法中判断如果使用jit会调用RegisterDexFiles,RegisterDexFiles方法中满足一系列判断条件会调用AddTask | thread_pool_->AddTask(Thread::Current(), new JitProfileTask(dex_files, class_loader)); |
CompileMethodFromProfile | Task* task = new JitCompileTask(method, JitCompileTask::TaskKind::kPreCompile, CompilationKind::kOptimized);thread_pool_->AddTask(self, task); |
CompileMethodsFromProfile | JitDoneCompilingProfileTask* task = new JitDoneCompilingProfileTask(dex_files);thread_pool_->AddTask(self, task); |
在java方法执行时,解释器在MethodEntered方法中根据采样率(AddSamples)决定是否执行MaybeCompileMethod | thread_pool_->AddTask(self,new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kBaseline));orthread_pool_->AddTask(self,new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOsr)); |
在 quick_entrypoints_arm.s 等汇编文件中,art_quick_invoke_stub 或 art_quick_resolution_trampoline 等入口函数会检查目标方法是否已编译优化。若未优化,则通过调用 artCompileOptimized 触发编译,并更新 ArtMethod::entry_point_from_compiled_code_ 字段指向新生成的机器码 | Runtime::Current()->GetJit()->EnqueueOptimizedCompilation(method, self);thread_pool_->AddTask(self,new JitCompileTask(method,itCompileTask::TaskKind::kCompile,CompilationKind::kOptimized)); |
Nterp解释器的NterpHotMethod方法,会调用EnqueueCompilationFromNterp将方法加入 JIT 编译队列 | thread_pool_->AddTask(self,new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kBaseline));orthread_pool_->AddTask(self,new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOsr));orthread_pool_->AddTask(self,new JitCompileTask(method,itCompileTask::TaskKind::kCompile,CompilationKind::kOptimized)); |
Profile Saver 线程
Profile Saver 线程是在StartProfileSaver方法中启动的,需要保证dalvik.vm.usejitprofiles为true才会启动该线程, 入口函数是RunProfileSaverThread;
void ProfileSaver::Start(const ProfileSaverOptions& options,
const std::string& output_filename,
jit::JitCodeCache* jit_code_cache,
const std::vector<std::string>& code_paths,
const std::string& ref_profile_filename) {
// Create a new thread which does the saving.
CHECK_PTHREAD_CALL(
pthread_create,
(&profiler_pthread_, nullptr, &RunProfileSaverThread, reinterpret_cast<void*>(instance_)),
"Profile saver thread");
SetProfileSaverThreadPriority(profiler_pthread_, kProfileSaverPthreadPriority);
}
从trace 中有能看出来,线程启动之后,在run方法中循环:
"Profile Saver" daemon prio=5 tid=15 Native
| group="system" sCount=1 ucsCount=0 flags=1 obj=0x13200670 self=0x7fb07783fe40
| sysTid=2090 nice=9 cgrp=top-app sched=0/0 handle=0x7fae9d030cf0
| state=S schedstat=( 1779215 1555034 12 ) utm=0 stm=0 core=1 HZ=100
| stack=0x7fae9cf3a000-0x7fae9cf3c000 stackSize=987KB
| held mutexes=
native: #00 pc 000000000005adf6 /apex/com.android.runtime/lib64/bionic/libc.so (syscall+22)
native: #01 pc 000000000042599e /apex/com.android.art/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+110)
native: #02 pc 00000000005e72e8 /apex/com.android.art/lib64/libart.so (art::ProfileSaver::Run()+552)
native: #03 pc 00000000005ed8cb /apex/com.android.art/lib64/libart.so (art::ProfileSaver::RunProfileSaverThread(void*)+171)
native: #04 pc 00000000000c758a /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+58)
native: #05 pc 000000000005fd87 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+55)
(no managed stack frames)
现在我们总结一下Profile Saver 线程run方法的核心逻辑:
- 在run方法进入之后,force_early_first_save为0, 首次的睡眠时间为5秒:kSaveResolvedClassesDelayMs = 5 * 1000; // 5 seconds
- 经历首次睡眠之后,广播Profile Saver 启动完成(NotifyStartupCompleted)
- 执行FetchAndCacheResolvedClassesAndMethods函数,统计目前jit_code_cache中hot method、sampled method 数量,如果方法出现的次数大于等于hot_method_sample_threshold,就记录为hot method,如大于0且小于hot_method_sample_threshold,则记录为sampled_method;
- 后面进入while(true)循环,先等待持锁,这里会计算sleep时间,后面根据GetMinSavePeriodMs()和sleep时间进行对比,保证每次循环sleep时间不小于 GetMinSavePeriodMs(),默认为40秒;
- 最后用ProcessProfilingInfo方法将热点函数落盘到profile文件中
用debuggerd或者是发生ANR异常抓取的trace中,有如下ProfileSaver的统计信息:
ProfileSaver total_bytes_written=0
ProfileSaver total_number_of_writes=0
ProfileSaver total_number_of_code_cache_queries=3
ProfileSaver total_number_of_skipped_writes=3
ProfileSaver total_number_of_failed_writes=0
ProfileSaver total_ms_of_sleep=5464197
ProfileSaver total_ms_of_work=2
ProfileSaver total_number_of_hot_spikes=0
ProfileSaver total_number_of_wake_ups=3
总结
本文仅对Jit thread 和Profile saver thread工作流程进行了简单介绍,并未对解释器、方法编译等内容进行深入分析,后续有时间会进一步更新相关内容。