JVM源码系列:JVM内部运行之Class的Method
Posted raintungli
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM源码系列:JVM内部运行之Class的Method相关的知识,希望对你有一定的参考价值。
1. Class的属性
在JVM中,通常一个class会初始化成Klass(接口), InstanceKlass(实例), Method(方法), ConstantsPool(常量区)
在上图我们可以看到一个大概的Method,ConstantsPool,InstanceKlass之间的关系
- InstanceKlass 里面保存着ConstantPool指针Method指针的数组
- Method对象里保存着ConstMethod指针
- ConstMethod里保存着ConstantsPool的指针
- ConstantsPool里保存着InstanceKlass的指针
我们可以通过Method -> ConstMethod -> ConstantsPool ->InstanceKlass构建Method 和InstanceKlass关系
2. Method
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
2.1 MethodData
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中
2.2 ConstMethod
ConstMethod是Method的相关统计信息,不如代码大小,代码名字的索引,方法的序列号,本地参数的数量等
enum
_has_linenumber_table = 0x0001,
_has_checked_exceptions = 0x0002,
_has_localvariable_table = 0x0004,
_has_exception_table = 0x0008,
_has_generic_signature = 0x0010,
_has_method_parameters = 0x0020,
_is_overpass = 0x0040,
_has_method_annotations = 0x0080,
_has_parameter_annotations = 0x0100,
_has_type_annotations = 0x0200,
_has_default_annotations = 0x0400
;
// Bit vector of signature
// Callers interpret 0=not initialized yet and
// -1=too many args to fix, must parse the slow way.
// The real initial value is special to account for nonatomicity of 64 bit
// loads and stores. This value may updated and read without a lock by
// multiple threads, so is volatile.
volatile uint64_t _fingerprint;
ConstantPool* _constants; // Constant pool
// Raw stackmap data for the method
Array<u1>* _stackmap_data;
int _constMethod_size;
u2 _flags;
u1 _result_type; // BasicType of result
// Size of Java bytecodes allocated immediately after Method*.
u2 _code_size;
u2 _name_index; // Method name (index in constant pool)
u2 _signature_index; // Method signature (index in constant pool)
u2 _method_idnum; // unique identification number for the method within the class
// initially corresponds to the index into the methods array.
// but this may change with redefinition
u2 _max_stack; // Maximum number of entries on the expression stack
u2 _max_locals; // Number of local variables used by this method
u2 _size_of_parameters; // size of the parameter block (receiver + arguments) in words
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指针
2.3 nmethod
nmethod全名native method,指向的是Java method编译的一个版本。当一个方法被JNI编译后会生成一个nmethod,指向的是编译的代码,整个 nmethod结构包含如下:
// - header (the nmethod structure)
// [Relocation]
// - relocation information
// - constant part (doubles, longs and floats used in nmethod)
// - oop table
// [Code]
// - code body
// - exception handler
// - stub code
// [Debugging information]
// - oop array
// - data array
// - pcs
// [Exception handler table]
// - handler entry point array
// [Implicit Null Pointer exception table]
// - implicit null table array
里面包含的代码段,异常处理等,有一个指向method的指针,和指向method*的指针jmethodID
nmethod本质是一个codeblob,被保存在codecache中的CodeHeap推区的HeapBlock块中。Method保存着最新的nmethod的指针,同时nmethod也有指向被编译的Method的指针。在上图的表示中,我们可以通过轮训整个codecache的codeheap区获取到所有已经编译的nmethod
2.3.1 nmethod的状态
enum in_use = 0, // executable nmethod
not_entrant = 1, // marked for deoptimization but activations may still exist,
// will be transformed to zombie when all activations are gone
zombie = 2, // no activations exist, nmethod is ready for purge
unloaded = 3 ; // there should be no activations, should not be called,
// 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的状态下还是代表存活的状态
2.3.2 nmethod编译
nmethod是通过CompilerThread线程(JavaThread)进行独立编译的,对不同的C1,C2 级别会构建不同数量的线程,默认是C1是1个线程,C2是2个线程,该线程数量无法通过参数设置改变线程数量
线程会去轮训CompileQueue的队列,CompileQueue里会保存着每个要执行的CompileTask,当CompileQueue的任务执行完后线程会堵塞
void CompileBroker::compiler_thread_loop()
CompilerThread* thread = CompilerThread::current();
CompileQueue* queue = thread->queue();
...
// Poll for new compilation tasks as long as the JVM runs. Compilation
// should only be disabled if something went wrong while initializing the
// compiler runtimes. This, in turn, should not happen. The only known case
// when compiler runtime initialization fails is if there is not enough free
// space in the code cache to generate the necessary stubs, etc.
while (!is_compilation_disabled_forever())
// We need this HandleMark to avoid leaking VM handles.
HandleMark hm(thread);
if (CodeCache::unallocated_capacity() < CodeCacheMinimumFreeSpace)
// the code cache is really full
handle_full_code_cache();
CompileTask* task = queue->get();
if (task == NULL)
continue;
// Give compiler threads an extra quanta. They tend to be bursty and
// this helps the compiler to finish up the job.
if( CompilerThreadHintNoPreempt )
os::hint_no_preempt();
// trace per thread time and compile statistics
CompilerCounters* counters = ((CompilerThread*)thread)->counters();
PerfTraceTimedEvent(counters->time_counter(), counters->compile_counter());
// Assign the task to the current thread. Mark this compilation
// thread as active for the profiler.
CompileTaskWrapper ctw(task);
nmethodLocker result_handle; // (handle for the nmethod produced by this task)
task->set_code_handle(&result_handle);
methodHandle method(thread, task->method());
// Never compile a method if breakpoints are present in it
if (method()->number_of_breakpoints() == 0)
// Compile the method.
if ((UseCompiler || AlwaysCompileLoopMethods) && CompileBroker::should_compile_new_jobs())
invoke_compiler_on_method(task);
else
// After compilation is disabled, remove remaining methods from queue
method->clear_queued_for_compilation();
task->set_failure_reason("compilation is disabled");
// Shut down compiler runtime
shutdown_compiler_runtime(thread->compiler(), thread);
当该方法有断点的时候并不进行编译,当参数-XX:-BackgroundCompilation设置成不是后台编译的时候,并不代表是在用户线程编译,而是提交任务CompileTask到CompileQueue,唯一的区别是堵塞当前线程等待CompileThread直到Task编译成功
void CompileBroker::compile_method_base(methodHandle method,
int osr_bci,
int comp_level,
methodHandle hot_method,
int hot_count,
const char* comment,
Thread* thread)
......
if (blocking)
wait_for_completion(task);
2.3.3 nmethod的回收(sweep)
在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的时候促发
void NMethodSweeper::mark_active_nmethods()
assert(SafepointSynchronize::is_at_safepoint(), "must be executed at a safepoint");
// If we do not want to reclaim not-entrant or zombie methods there is no need
// to scan stacks
if (!MethodFlushing)
return;
// Increase time so that we can estimate when to invoke the sweeper again.
_time_counter++;
// Check for restart
assert(CodeCache::find_blob_unsafe(_current) == _current, "Sweeper nmethod cached state invalid");
if (!sweep_in_progress())
_seen = 0;
_sweep_fractions_left = NmethodSweepFraction;
_current = CodeCache::first_nmethod();
_traversals += 1;
_total_time_this_sweep = Tickspan();
Threads::nmethods_do(&mark_activation_closure);
else
// Only set hotness counter
tty->print_cr("### Sweep mark_active_nmethods not in progress %d /" PTR_FORMAT " ", _traversals, _current);
Threads::nmethods_do(&set_hotness_closure);
OrderAccess::storestore();
在函数里我们看到重置了_seen 和 _sweep_fractions_left,而并不是每个nmethod进入not_entrant都能进入zombie状态,同时要不能被VM调用或者ServiceThread
bool nmethod::can_convert_to_zombie()
assert(is_not_entrant(), "must be a non-entrant method");
// Since the nmethod sweeper only does partial sweep the sweeper's traversal
// count can be greater than the stack traversal count before it hits the
// nmethod for the second time.
tty->print_cr("### nmethod stack_traversal_mark %d %d",stack_traversal_mark(),NMethodSweeper::traversal_count());
return stack_traversal_mark()+1 < NMethodSweeper::traversal_count() &&
!is_locked_by_vm();
以上是关于JVM源码系列:JVM内部运行之Class的Method的主要内容,如果未能解决你的问题,请参考以下文章