标签:代码段 atom ted 状态 actions todo 数据 渐进 exist
在JVM中,通常一个class会初始化成Klass(接口), InstanceKlass(实例), Method(方法), ConstantsPool(常量区)
在上图我们可以看到一个大概的Method,ConstantsPool,InstanceKlass之间的关系
InstanceKlass 里面保存着ConstantPool指针,Method指针的数组
Method对象里保存着ConstMethod指针
ConstMethod里保存着ConstantsPool的指针
ConstantsPool里保存着InstanceKlass的指针
我们可以通过Method -> ConstMethod -> ConstantsPool ->InstanceKlass构建Method 和InstanceKlass关系
Method结构体如下,我们可以看到在Method的只是一个载体,保存着和方法区相关的指针
// |------------------------------------------------------| // | header | // | klass | // |------------------------------------------------------| // | ConstMethod* (oop) | // |------------------------------------------------------| // | methodData (oop) | // | methodCounters | // |------------------------------------------------------| // | access_flags | // | vtable_index | // |------------------------------------------------------| // | result_index (C++ interpreter only) | // |------------------------------------------------------| // | method_size | intrinsic_id| flags | // |------------------------------------------------------| // | code (pointer) | // | i2i (pointer) | // | adapter (pointer) | // | from_compiled_entry (pointer) | // | from_interpreted_entry (pointer) | // |------------------------------------------------------| // | native_function (present only if native) | // | signature_handler (present only if native) | // |------------------------------------------------------| class Method : public Metadata { friend class VMStructs; private: ConstMethod* _constMethod; // Method read-only data. MethodData* _method_data; MethodCounters* _method_counters; AccessFlags _access_flags; // Access flags int _vtable_index; // vtable index of this method (see VtableIndexFlag) ... nmethod* volatile _code; // Points to the corresponding piece of native code }
我们主要介绍Method结构体中的MethodData、ConstMethod、nmethod
MethodData结构基础是ProfileData,记录函数运行状态下的数据,通常JIT编译的时候有多个Level,C2级别编译下需要进行编译优化的统计分析的数据,下表是几种JIT的编译级别
* The system supports 5 execution levels: * * level 0 - interpreter * * level 1 - C1 with full optimization (no profiling) * * level 2 - C1 with invocation and backedge counters * * level 3 - C1 with full profiling (level 2 + MDO) * * level 4 - C2
MethodData里面分为3个部分,一个是函数类型等运行相关统计数据,一个是参数类型运行相关统计数据,还有一个是extra扩展区保存着deoptimization的相关信息,整个内存情况请参考下图
我们介绍一种extra扩展区的SpeculativeTrapData类型,这是在JVM在进行的deoptimization method A过程中会反向的将method A指针保存到method A调用的Method的Extra data中
ConstMethod是Method的相关统计信息,如代码大小,代码名字的索引,方法的序列号,本地参数的数量等
1 enum { 2 _has_linenumber_table = 0x0001, 3 _has_checked_exceptions = 0x0002, 4 _has_localvariable_table = 0x0004, 5 _has_exception_table = 0x0008, 6 _has_generic_signature = 0x0010, 7 _has_method_parameters = 0x0020, 8 _is_overpass = 0x0040, 9 _has_method_annotations = 0x0080, 10 _has_parameter_annotations = 0x0100, 11 _has_type_annotations = 0x0200, 12 _has_default_annotations = 0x0400 13 }; 14 15 // Bit vector of signature 16 // Callers interpret 0=not initialized yet and 17 // -1=too many args to fix, must parse the slow way. 18 // The real initial value is special to account for nonatomicity of 64 bit 19 // loads and stores. This value may updated and read without a lock by 20 // multiple threads, so is volatile. 21 volatile uint64_t _fingerprint; 22 23 ConstantPool* _constants; // Constant pool 24 25 // Raw stackmap data for the method 26 Array<u1>* _stackmap_data; 27 28 int _constMethod_size; 29 u2 _flags; 30 u1 _result_type; // BasicType of result 31 32 // Size of Java bytecodes allocated immediately after Method*. 33 u2 _code_size; 34 u2 _name_index; // Method name (index in constant pool) 35 u2 _signature_index; // Method signature (index in constant pool) 36 u2 _method_idnum; // unique identification number for the method within the class 37 // initially corresponds to the index into the methods array. 38 // but this may change with redefinition 39 u2 _max_stack; // Maximum number of entries on the expression stack 40 u2 _max_locals; // Number of local variables used by this method 41 u2 _size_of_parameters; // size of the parameter block (receiver + arguments) in words 42 u2 _orig_method_idnum; // Original unique identification number for the method
其中_constants,_method_idnum这两个是链接method的参数,因为method有constMethod指针,但constMethod没有method的指针,如何通过constMethod来获取Method,需要通过ConstantPool -> InstanceKlass -> Method数组->第_method_idnum个来获取Method指针
nmethod全名native method,指向的是Java method编译的一个版本。当一个方法被JNI编译后会生成一个nmethod,指向的是编译的代码,整个 nmethod结构包含如下:
1 // - header (the nmethod structure) 2 // [Relocation] 3 // - relocation information 4 // - constant part (doubles, longs and floats used in nmethod) 5 // - oop table 6 // [Code] 7 // - code body 8 // - exception handler 9 // - stub code 10 // [Debugging information] 11 // - oop array 12 // - data array 13 // - pcs 14 // [Exception handler table] 15 // - handler entry point array 16 // [Implicit Null Pointer exception table] 17 // - implicit null table array
里面包含的代码段,异常处理等,有一个指向method的指针,和指向method*的指针jmethodID
nmethod本质是一个codeblob,被保存在codecache中的CodeHeap堆区的HeapBlock块中。Method保存着最新的nmethod的指针,同时nmethod也有指向被编译的Method的指针。在上图的表示中,我们可以通过轮训整个codecache的codeheap区获取到所有已经编译的nmethod
1 enum { in_use = 0, // executable nmethod 2 not_entrant = 1, // marked for deoptimization but activations may still exist, 3 // will be transformed to zombie when all activations are gone 4 zombie = 2, // no activations exist, nmethod is ready for purge 5 unloaded = 3 }; // there should be no activations, should not be called, 6 // will be transformed to zombie immediately
nmethod有4种状态:
0代表这还在使用
1表示可以被转化成zombie的状态,但依然存活
2是zombie状态代表nmethod可被回收
3是not_entrant和zombie的中间状态,表示可以立刻转化成zombie状态
nmethod的状态的改变是通过sweeper来改变,比如在deoptimization场景下原来已经编译的nmethod设置成not_entrant的状态,通过sweeper最后转化成了zombie的状态后被回收,而当nmethod在0,1的状态下还是代表存活的状态
nmethod是通过CompilerThread线程(JavaThread)进行独立编译的,对不同的C1,C2 级别会构建不同数量的线程,默认是C1是1个线程,C2是2个线程,该线程数量无法通过参数设置改变线程数量
线程会去轮训CompileQueue的队列,CompileQueue里会保存着每个要执行的CompileTask,当CompileQueue的任务执行完后线程会堵塞
1 void CompileBroker::compiler_thread_loop() { 2 CompilerThread* thread = CompilerThread::current(); 3 CompileQueue* queue = thread->queue(); 4 ... 5 // Poll for new compilation tasks as long as the JVM runs. Compilation 6 // should only be disabled if something went wrong while initializing the 7 // compiler runtimes. This, in turn, should not happen. The only known case 8 // when compiler runtime initialization fails is if there is not enough free 9 // space in the code cache to generate the necessary stubs, etc. 10 while (!is_compilation_disabled_forever()) { 11 // We need this HandleMark to avoid leaking VM handles. 12 HandleMark hm(thread); 13 14 if (CodeCache::unallocated_capacity() < CodeCacheMinimumFreeSpace) { 15 // the code cache is really full 16 handle_full_code_cache(); 17 } 18 19 CompileTask* task = queue->get(); 20 if (task == NULL) { 21 continue; 22 } 23 24 // Give compiler threads an extra quanta. They tend to be bursty and 25 // this helps the compiler to finish up the job. 26 if( CompilerThreadHintNoPreempt ) 27 os::hint_no_preempt(); 28 29 // trace per thread time and compile statistics 30 CompilerCounters* counters = ((CompilerThread*)thread)->counters(); 31 PerfTraceTimedEvent(counters->time_counter(), counters->compile_counter()); 32 33 // Assign the task to the current thread. Mark this compilation 34 // thread as active for the profiler. 35 CompileTaskWrapper ctw(task); 36 nmethodLocker result_handle; // (handle for the nmethod produced by this task) 37 task->set_code_handle(&result_handle); 38 methodHandle method(thread, task->method()); 39 40 // Never compile a method if breakpoints are present in it 41 if (method()->number_of_breakpoints() == 0) { 42 // Compile the method. 43 if ((UseCompiler || AlwaysCompileLoopMethods) && CompileBroker::should_compile_new_jobs()) { 44 invoke_compiler_on_method(task); 45 } else { 46 // After compilation is disabled, remove remaining methods from queue 47 method->clear_queued_for_compilation(); 48 task->set_failure_reason("compilation is disabled"); 49 } 50 } 51 } 52 53 // Shut down compiler runtime 54 shutdown_compiler_runtime(thread->compiler(), thread); 55 }
当该方法有断点的时候并不进行编译,当参数-XX:-BackgroundCompilation设置成不是后台编译的时候,并不代表是在用户线程编译,而是提交任务CompileTask到CompileQueue,唯一的区别是堵塞当前线程等待CompileThread直到Task编译成功
1 void CompileBroker::compile_method_base(methodHandle method, 2 int osr_bci, 3 int comp_level, 4 methodHandle hot_method, 5 int hot_count, 6 const char* comment, 7 Thread* thread) { 8 ...... 9 10 if (blocking) { 11 wait_for_completion(task); 12 } 13 }
在2.3.2中描述过,nmethod进入zombie状态,代表着该nmethod会回收,nmethod是在GC的时候被回收的么?并不是而是在接收CompileTask的时候由CompileThread来触发,但是并不代表着GC无关,这里有个比较复杂的算法,在GC的时候会更改一些统计数据。
CompileThread 满足下列条件会触发nmethod 进行sweep函数NMethodSweeper::possibly_sweep();
一轮sweep :代表从nmethod从头到尾一次回收
当从CompileQueue里获取到ComileTask的时候触发
当从CompileQueue里没有获取到ComileTask的时候,在等待一定的时间NmethodSweepCheckInterval(default=5) * 1000后触发
当从CodeCache已经满的情况下(-XX:ReservedCodeCacheSize=100M)
当然触发possibly_sweep并不代表一定会触发一定去回收nmethod
在上图我们可以看到整一个nmethod的回收是个复杂的算法
首先取决于你的-XX:ReservedCodeCacheSize参数,JVM会以16M为一次,所允许的最大次数为ReservedCodeCacheSize/16M
_time_counter代表已经进入Softpoint的次数(也就是前面提到的GC)
_last_sweep代表着已经sweep完成的次数
CodeCache::reverse_free_ratio()=最大的容量/空闲的内存
_sweep_fractions_left 默认值ReservedCodeCacheSize/16M当调用possibly_sweep的时候会减去1,当等于0的时候代表sweep 结束(设置_should_sweep =false),需要等到进入softpoint才能重置会ReservedCodeCacheSize/16M
我们可以看到当进入Softpoint次数越多,空闲的内存越少越会促发sweep code cache, 而调用函数NMethodSweeper::sweep_code_cache并不代表一定会回收所有的nmethod,在函数里还有一个 int todo = (CodeCache::nof_nmethods() - _seen) / _sweep_fractions_left;来控制这次轮训多少个nmethod,_seen代表这一轮sweep 已经轮训的个数,CodeCache::nof_nmethods()代表nmethods的总数,_sweep_fractions_left就是前面定义的,我们可以看到在sweep_code_cache并不是一次全部轮询所有的nmethods,而是将剩余的总数/_sweep_fractions_left的数量,越到一轮的sweep后期清除的数量越多,是一个渐进式的回收方式。
函数NMethodSweeper::mark_active_nmethods()会被进入softpoint的时候触发
1 void NMethodSweeper::mark_active_nmethods() { 2 assert(SafepointSynchronize::is_at_safepoint(), "must be executed at a safepoint"); 3 // If we do not want to reclaim not-entrant or zombie methods there is no need 4 // to scan stacks 5 if (!MethodFlushing) { 6 return; 7 } 8 9 // Increase time so that we can estimate when to invoke the sweeper again. 10 _time_counter++; 11 12 // Check for restart 13 assert(CodeCache::find_blob_unsafe(_current) == _current, "Sweeper nmethod cached state invalid"); 14 if (!sweep_in_progress()) { 15 _seen = 0; 16 _sweep_fractions_left = NmethodSweepFraction; 17 _current = CodeCache::first_nmethod(); 18 _traversals += 1; 19 _total_time_this_sweep = Tickspan(); 20 Threads::nmethods_do(&mark_activation_closure); 21 22 } else { 23 // Only set hotness counter 24 tty->print_cr("### Sweep mark_active_nmethods not in progress %d /" PTR_FORMAT " ", _traversals, _current); 25 Threads::nmethods_do(&set_hotness_closure); 26 } 27 28 OrderAccess::storestore(); 29 }
在函数里我们看到重置了_seen 和 _sweep_fractions_left,而并不是每个nmethod进入not_entrant都能进入zombie状态,同时要不能被VM调用或者ServiceThread
1 bool nmethod::can_convert_to_zombie() { 2 assert(is_not_entrant(), "must be a non-entrant method"); 3 4 // Since the nmethod sweeper only does partial sweep the sweeper‘s traversal 5 // count can be greater than the stack traversal count before it hits the 6 // nmethod for the second time. 7 tty->print_cr("### nmethod stack_traversal_mark %d %d",stack_traversal_mark(),NMethodSweeper::traversal_count()); 8 return stack_traversal_mark()+1 < NMethodSweeper::traversal_count() && 9 !is_locked_by_vm(); 10 }
标签:代码段 atom ted 状态 actions todo 数据 渐进 exist
原文地址:https://www.cnblogs.com/yelao/p/12515210.html