native方法调用流程剖析

JVM启动阶段

JVM启动时,会为native方法生成解释器入口,调用链为Threads::create_vm->init_globals->interpreter_init_code-> Interpreter::initialize_code()->TemplateInterpreterGenerator::TemplateInterpreterGenerator->TemplateInterpreterGenerator::generate_all()->TemplateInterpreterGenerator.generate_method_entry->TemplateInterpreterGenerator::generate_native_entry->InterpreterRuntime::prepare_native_call->NativeLookup::lookup->NativeLookup::lookup_base->NativeLookup::lookup_entry->NativeLookup::pure_jni_name

// all non-native method kinds
  method_entry(zerolocals)
  method_entry(zerolocals_synchronized)
  method_entry(empty)
  method_entry(getter)
  method_entry(setter)
  method_entry(abstract)
  method_entry(java_lang_math_sin  )
  method_entry(java_lang_math_cos  )
  method_entry(java_lang_math_tan  )
  method_entry(java_lang_math_abs  )
  method_entry(java_lang_math_sqrt )
  method_entry(java_lang_math_log  )
  method_entry(java_lang_math_log10)
  method_entry(java_lang_math_exp  )
  method_entry(java_lang_math_pow  )
  method_entry(java_lang_math_fmaF )
  method_entry(java_lang_math_fmaD )
  method_entry(java_lang_ref_reference_get)

// all native method kinds (must be one contiguous block)
  Interpreter::_native_entry_begin = Interpreter::code()->code_end();
  method_entry(native)
  method_entry(native_synchronized)
  Interpreter::_native_entry_end = Interpreter::code()->code_end();

  method_entry(java_util_zip_CRC32_update)
  method_entry(java_util_zip_CRC32_updateBytes)
  method_entry(java_util_zip_CRC32_updateByteBuffer)
  method_entry(java_util_zip_CRC32C_updateBytes)
  method_entry(java_util_zip_CRC32C_updateDirectByteBuffer)

  method_entry(java_lang_Float_intBitsToFloat);
  method_entry(java_lang_Float_floatToRawIntBits);
  method_entry(java_lang_Double_longBitsToDouble);
  method_entry(java_lang_Double_doubleToRawLongBits);
char* NativeLookup::pure_jni_name(const methodHandle& method) {
  stringStream st;
  // Prefix
  st.print("Java_");
  // Klass name
  if (!map_escaped_name_on(&st, method->klass_name())) {
    return NULL;
  }
  st.print("_");
  // Method name
  if (!map_escaped_name_on(&st, method->name())) {
    return NULL;
  }
  return st.as_string();
}

举例说明:
java.lang.Object.getClass最终会调用Java_java_lang_Object_getClass(JNIEnv *env, jobject this) 方法;

link阶段

通常在该方法第一次被调用的时候触发,invoke字节码指令执行时要先检查调用目标是否已经resolve好了,没有的话就要做resolution。这个路径会把某条invoke字节码指令的参数的符号链接解析(resolve)为实际的method指针然后存在constant pool cache里。这样,接下来解释器就可以通过解析好的method指针找到from_interpreted_entry()进入native方法的解释器入口;

类初始化的时候会调用InstanceKlass::initialize->InstanceKlass::initialize_impl->InstanceKlass::link_class->InstanceKlass::link_class_impl->InstanceKlass::link_methods->Method::link_method

address entry = Interpreter::entry_for_method(h_method);
set_interpreter_entry(entry);
static address    entry_for_method(const methodHandle& m)     { return entry_for_kind(method_kind(m)); }
static address    entry_for_kind(MethodKind k)                { 
return _entry_table[k]; }

可以看到此处会从_entry_table中根据方法类型获取entry points,将method和entry points关联起来,记录在_from_interpreted_entry和_i2i_entry属性中;

调用阶段

  1. 当某个native方法被调用时,JavaCalls::call_helper会通过method->from_interpreted_entry()读取_from_interpreted_entry属性获取方法的entry point;这就进到最初提到的TemplateInterpreterGenerator::generate_native_entry所生成的代码;
    它会调用InterpreterRuntime::prepare_native_call来获取native函数真正的入口地址;
  2. 这里会先调用m->has_native_function()看之前是否已经在Method对象里记录下了native函数入口地址;
  3. 如果已有地址的话可能是JNI库在JNI_OnLoad()的时候调用了RegisterNatives()来注册函数地址信息,或者可能这已经不是第一调用该native方法;
  4. 如果没有记录下函数地址,就调用NativeLookup::lookup来寻找native方法真正的目标在什么地方,然后把它记在Method里;
  5. NativeLookup::lookup()里会通过NativeLookup::pure_jni_name()来构造出符合JNI规范的函数名,然后通过os::dll_lookup()在查找路径中能找到的动态链接库里去找这个名字对应的地址;
  6. Method里有方法调用次数的计数器,而native entry里有递增这个计数器的逻辑;
if (inc_counter) {
    generate_counter_incr(&invocation_counter_overflow);
  }
void TemplateInterpreterGenerator::generate_counter_incr(Label* overflow) {
  Label done;
  // Note: In tiered we increment either counters in Method* or in MDO depending if we're profiling or not.
  int increment = InvocationCounter::count_increment;
  Label no_mdo;
  __ bind(no_mdo);
  // Increment counter in MethodCounters
  const Address invocation_counter(rax,
      MethodCounters::invocation_counter_offset() +
      InvocationCounter::counter_offset());
  __ get_method_counters(rbx, rax, done);
  const Address mask(rax, in_bytes(MethodCounters::invoke_mask_offset()));
  __ increment_mask_and_jump(invocation_counter, increment, mask, rcx,
      false, Assembler::zero, overflow);
  __ bind(done);
}
  1. 当一个native方法被调用足够多次之后,HotSpot会为它生成专门的入口(替换掉原本通用的解释器入口)。这种入口叫做native wrapper。Signature相同的native方法共享同一个native wrapper;

调用链为CompileBroker::compile_method()
-> AdapterHandlerLibrary::create_native_wrapper(method)
-> SharedRuntime::generate_native_wrapper()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容