Jit/Profile Saver 线程工作流程-基于Android S

56 阅读6分钟

背景

经常在分析问题或者看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的参数,我们用到的时候再进行分析。

ProfileSaver1.png

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方法中满足一系列判断条件会调用AddTaskthread_pool_->AddTask(Thread::Current(), new JitProfileTask(dex_files, class_loader));
CompileMethodFromProfileTask* task = new JitCompileTask(method, JitCompileTask::TaskKind::kPreCompile, CompilationKind::kOptimized);thread_pool_->AddTask(self, task);
CompileMethodsFromProfileJitDoneCompilingProfileTask* task = new JitDoneCompilingProfileTask(dex_files);thread_pool_->AddTask(self, task);
在java方法执行时,解释器在MethodEntered方法中根据采样率(AddSamples)决定是否执行MaybeCompileMethodthread_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文件中

Jit_Profile Saver工作流程-基于Android S-流程图.jpg 用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工作流程进行了简单介绍,并未对解释器、方法编译等内容进行深入分析,后续有时间会进一步更新相关内容。