• Runtime CodeCache Sweeper


    1. sweeper线程创建

    1.1 什么是sweeper线程

    Threads::create_vm是虚拟机创生纪,其中就包括jit编译器线程和codecache sweeper线程的创建:

    void CompileBroker::init_compiler_sweeper_threads() {
      ...
      if (MethodFlushing) {
        // Initialize the sweeper thread
        Handle thread_oop = create_thread_oop("Sweeper thread", CHECK);
        jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop());
        make_thread(thread_handle, NULL, NULL, CHECK);
      }
    }
    

    这个sweeper thread就是专门用来清理codecache的线程,而codecache是用来存放代码片段的地方,比如jit compiler编译后的方法代码,虚拟机内部用到的一些stub和adapter代码片段。

    回到上面的代码,make_thread将会创建一个线程,然后将线程的入口设置为NMethodSweeper::sweeper_loop方法:

    void NMethodSweeper::sweeper_loop() {
      bool timeout;
      while (true) {
        {
          ThreadBlockInVM tbivm(JavaThread::current());
          MutexLockerEx waiter(CodeCache_lock, Mutex::_no_safepoint_check_flag);
          const long wait_time = 60*60*24 * 1000;
          timeout = CodeCache_lock->wait(Mutex::_no_safepoint_check_flag, wait_time);
        }
        if (!timeout) {
          possibly_sweep();
        }
      }
    }
    

    获取CodeCache_lock之后wait在这里,而一般wait阻塞之后的线程会因为各种原因唤醒,比如os导致的假醒,其他线程调用CodeCache_lock->notify(),又或者超时。这里的逻辑是,如果线程因为超时醒来,就不做sweep操作,否则清理codecache,而这个possibly_sweep就是sweeper线程的核心逻辑了。

    换句话说,只有其他线程notify这个codecache sweeper线程醒来,codecache sweeper线程才可能清理codecache。

    1.2 清理codecache的时机

    那么,有哪些地方可能notify清理线程醒来呢?有两个地方,每次在codecache分配空间的时候会notify,还有一处是JVM的白盒测试可以强制notify清理线程。

    // 白盒测试强制notify
    WB_ENTRY(void, WB_ForceNMethodSweep(JNIEnv* env, jobject o))
      NMethodSweeper::force_sweep();
    WB_END
    // codecache分配内存日常notify
    CodeBlob* CodeCache::allocate(int size, int code_blob_type, int orig_code_blob_type) {
      // Possibly wakes up the sweeper thread.
      NMethodSweeper::notify(code_blob_type);
      assert_locked_or_safepoint(CodeCache_lock);
      ...
    }
    

    所以正常情况下可能发生codecache清理的时刻就是每次向codecache申请空间的时候。

    2. sweeper的核心逻辑

    2.1 判断是否清理

    说完了分配清理时机,接下来是清理逻辑,因为前面多次提到是可能清理,不是一定清理,从名字possibly_sweep也能看出来。注释已经总结了,有三种情况可能清理codecache:

    1. codecache要满了
    2. 从上传清理到现在有足够多的状态改变
    3. 很久没清理了
    /**
     * This function invokes the sweeper if at least one of the three conditions is met:
     *    (1) The code cache is getting full
     *    (2) There are sufficient state changes in/since the last sweep.
     *    (3) We have not been sweeping for 'some time'
     */
    void NMethodSweeper::possibly_sweep() {
      assert(JavaThread::current()->thread_state() == _thread_in_vm, "must run in vm mode");
      if (!_should_sweep) {
        const int time_since_last_sweep = _time_counter - _last_sweep;
        const int max_wait_time = ReservedCodeCacheSize / (16 * M);
        double wait_until_next_sweep = max_wait_time - time_since_last_sweep -
            MAX2(CodeCache::reverse_free_ratio(CodeBlobType::MethodProfiled),
                 CodeCache::reverse_free_ratio(CodeBlobType::MethodNonProfiled));
        assert(wait_until_next_sweep <= (double)max_wait_time, "Calculation of code cache sweeper interval is incorrect");
        if ((wait_until_next_sweep <= 0.0) || !CompileBroker::should_compile_new_jobs()) {
          _should_sweep = true;
        }
      }
      // Remember if this was a forced sweep
      bool forced = _force_sweep;
      // Force stack scanning if there is only 10% free space in the code cache.
      // We force stack scanning only if the non-profiled code heap gets full, since critical
      // allocations go to the non-profiled heap and we must be make sure that there is
      // enough space.
      double free_percent = 1 / CodeCache::reverse_free_ratio(CodeBlobType::MethodNonProfiled) * 100;
      if (free_percent <= StartAggressiveSweepingAt) {
        do_stack_scanning();
      }
      if (_should_sweep || forced) {
        init_sweeper_log();
        sweep_code_cache();
      }
      // We are done with sweeping the code cache once.
      _total_nof_code_cache_sweeps++;
      _last_sweep = _time_counter;
      // Reset flag; temporarily disables sweeper
      _should_sweep = false;
      // If there was enough state change, 'possibly_enable_sweeper()'
      // sets '_should_sweep' to true
      possibly_enable_sweeper();
      // Reset _bytes_changed only if there was enough state change. _bytes_changed
      // can further increase by calls to 'report_state_change'.
      if (_should_sweep) {
        _bytes_changed = 0;
      }
      if (forced) {
        assert(_force_sweep, "Should be a forced sweep");
        MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
        _force_sweep = false;
        CodeCache_lock->notify();
      }
    }
    

    一个一个来说。

    十七行的if里面的两个条件,前者wait_until_next_sweep对应(3),逻辑见代码,后面的should_compile_new_jobs对应codecache对应(1):

    void CompileBroker::handle_full_code_cache(int code_blob_type) {
      UseInterpreter = true;
      if (UseCompiler || AlwaysCompileLoopMethods ) {
        ...
    #ifndef PRODUCT
        if (CompileTheWorld || ExitOnFullCodeCache) {
          codecache_print(/* detailed= */ true);
          before_exit(JavaThread::current());
          exit_globals(); // will delete tty
          vm_direct_exit(CompileTheWorld ? 0 : 1);
        }
    #endif
        if (UseCodeCacheFlushing) {
          // Since code cache is full, immediately stop new compiles
          if (CompileBroker::set_should_compile_new_jobs(CompileBroker::stop_compilation)) {
            NMethodSweeper::log_sweep("disable_compiler");
          }
        } else {
          disable_compilation_forever();
        }
        CodeCache::report_codemem_full(code_blob_type, should_print_compiler_warning());
      }
    }
    

    上面意思很清楚了,如果编译器发现codecache要满的时候,会用CompileBroker::set_should_compile_new_jobs设置不可编译热方法,然后等待sweeper线程清理,清理完后又用这个方法设置为可以继续编译热方法,这是后话。

    非产品级虚拟机还可以使用-XX:+ExitOnFullCodeCache在codecache满的时候直接终止虚拟机执行,谁说一定要清理了?!

    最后代码的46行possibly_enable_sweeper对应(2),他其实比较简单:

    void NMethodSweeper::possibly_enable_sweeper() {
      double percent_changed = ((double)_bytes_changed / (double)ReservedCodeCacheSize) * 100;
      if (percent_changed > 1.0) {
        _should_sweep = true;
      }
    }
    

    这里的意思是,只要codecache里面1%的状态改变了,那么就可以清理。说了半天,到底什么是状态改变?
    nmethod是虚拟机中用来表示编译后的java方法,他有很多状态:not_installed,in_use,zombie,not_entrant,unloaded。in_use表示这个java方法可以正常使用,not_entrant表示方法已经不可进入了,但还活着,可能还在栈上。最后zombie表示彻底死了,栈上也没了。unloaded表示nmethod已经清理完毕,不可使用。

    举个例子,c2会使用激进的内联策略编译那些非final的方法。如果后来某个时间,有一个子类重写了这个方法,这个被编译的代码就不能再使用了。这个方法即nmethod被标注为make_not_entrant,表示后续的调用不能再用它,但是现存的调用还可以使用它。当现存的调用使用完毕返回后,栈帧中就不存在指向它的eip了,这个nmethod继而被标注为zombie,可以被清理了。

    说了这么多,和sweeper相关的其实就一点,nmethod的状态变为not_entrant/zombie/unloaded时,上面的_bytes_changed就会增加,增加的数量是nmethod的大小,也就是java方法编译后的总大小。也就是说,如果一个方法有1M,codecache有5m,当方法被设置为unloaded的时候,相当于20%的状态改变了,此时可以发生清理。

    总结一下,首先是codecache的分配会调用sweeper,然后三种情况可能清理,接着可选的扫描栈,最后清理,这是本文核心主线,后面几步全部位于possibly_sweep。

    2.2 扫描栈

    扫描栈位于possibly_sweep里面31行的do_stack_scanning()。如果codecache只有10%的空余空间了,就会发生这一步(由产品级参数-XX:StartAggressiveSweepingAt=10控制)

    为什么说是可选呢??????

    它会投递给虚拟机线程一个VM_MarkActiveNMethods,促使虚拟机线程进入安全点,然后执行NMethodSweeper::mark_active_nmethods

    2.3 清理方法

    清理方法位于possibly_sweep里面的36行的sweep_code_cache,它遍历所有nmethod,然后用process_compiled_method判断是否清理,代码相当直观:

    NMethodSweeper::MethodStateChange NMethodSweeper::process_compiled_method(CompiledMethod* cm) {
      ...
      if (cm->is_locked_by_vm()) {
        // But still remember to clean-up inline caches for alive nmethods
        if (cm->is_alive()) {
          // Clean inline caches that point to zombie/non-entrant/unloaded nmethods
          MutexLocker cl(CompiledIC_lock);
          cm->cleanup_inline_caches();
          SWEEP(cm);
        }
        return result;
      }
      if (cm->is_zombie()) {
        assert(!cm->is_locked_by_vm(), "must not flush locked Compiled Methods");
        cm->flush();
        assert(result == None, "sanity");
        result = Flushed;
      } else if (cm->is_not_entrant()) {
        // If there are no current activations of this method on the
        // stack we can safely convert it to a zombie method
        OrderAccess::loadload(); // _stack_traversal_mark and _state
        if (cm->can_convert_to_zombie()) {
          {
            MutexLocker cl(CompiledIC_lock);
            cm->clear_ic_callsites();
          }
          // Code cache state change is tracked in make_zombie()
          cm->make_zombie();
          SWEEP(cm);
          if (cm->is_osr_method() && !cm->is_locked_by_vm()) {
            assert(cm->is_zombie(), "nmethod must be unregistered");
            cm->flush();
            assert(result == None, "sanity");
            result = Flushed;
          } else {
            assert(result == None, "sanity");
            result = MadeZombie;
            assert(cm->is_zombie(), "nmethod must be zombie");
          }
        } else {
          // Still alive, clean up its inline caches
          MutexLocker cl(CompiledIC_lock);
          cm->cleanup_inline_caches();
          SWEEP(cm);
        }
      } else if (cm->is_unloaded()) {
        // Code is unloaded, so there are no activations on the stack.
        // Convert the nmethod to zombie or flush it directly in the OSR case.
        if (cm->is_osr_method()) {
          SWEEP(cm);
          // No inline caches will ever point to osr methods, so we can just remove it
          cm->flush();
          assert(result == None, "sanity");
          result = Flushed;
        } else {
          // Code cache state change is tracked in make_zombie()
          cm->make_zombie();
          SWEEP(cm);
          assert(result == None, "sanity");
          result = MadeZombie;
        }
      } else {
        if (cm->is_nmethod()) {
          possibly_flush((nmethod*)cm);
        }
        // Clean inline caches that point to zombie/non-entrant/unloaded nmethods
        MutexLocker cl(CompiledIC_lock);
        cm->cleanup_inline_caches();
        SWEEP(cm);
      }
      return result;
    }
    

    process_compiled_method就是根据方法的状态,判断是否可以清理。比如,如果是zombie状态,那么可以清理,如果是not_entrant,能转成zombie就清理,不能转就暂时只清理inline cache,等等。

  • 相关阅读:
    第一阶段冲刺09
    英文单词统计
    第一阶段冲刺08
    暑假生活第二周
    暑假生活第一周
    大道至简读书笔记03
    个人总结15
    大道至简读书笔记02
    计算最长英语单词链
    个人总结14
  • 原文地址:https://www.cnblogs.com/kelthuzadx/p/15726506.html
Copyright © 2020-2023  润新知