专注虚拟机与编译器研究

第29篇-调用Java主类的main()方法

在第1篇中大概介绍过Java中主类方法main()的调用过程,这一篇介绍的详细一点,大概的调用过程如下图所示。

图片错误修正(如上为java或javaw,并不是java.exe和javaw.exe)

其中浅红色的函数由主线程执行,而另外的浅绿色部分由另外一个线程执行,这个线程最终也会负责执行Java主类中的main()方法。在JavaMain()函数中调用LoadMainClass()函数加载Java主类。接着在JavaMain()函数中有如下调用:

源代码位置:openjdk/jdk/src/share/bin/java.c

mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");

env为JNIEnv*类型。调用JNIEnv类型中定义的GetStaticMethodID()函数获取Java主类中main()方法的方法唯一ID,调用GetStaticMethodID()函数就是调用到jni_GetStaticMethodID()函数,实现如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jni.cpp

JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig))
  jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);
  return ret;
JNI_END


static jmethodID get_method_id(
   JNIEnv     *env,
   jclass     clazz,
   const char *name_str,
   const char *sig,
   bool       is_static,
   TRAPS
){
  const char *name_to_probe = (name_str == NULL)
                        ? vmSymbols::object_initializer_name()->as_C_string()
                        : name_str;
  TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe));
  TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));

  KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));

  // 保证java.lang.Class类已经初始化完成
  klass()->initialize(CHECK_NULL);

  Method* m;
  if ( name == vmSymbols::object_initializer_name() || 查找的是<init>方法
       name == vmSymbols::class_initializer_name() ) { 查找的是<clinit>方法
    // 因为要查找的是构造函数,构造函数没有继承特性,所以当前类找不到时不向父类中继续查找
    if (klass->oop_is_instance()) {
       // find_method()函数不会向上查找
       m = InstanceKlass::cast(klass())->find_method(name, signature); 
    } else {
       m = NULL;
    }
  } else {
    // lookup_method()函数会向上查找
    m = klass->lookup_method(name, signature); 
    if (m == NULL &&  klass->oop_is_instance()) {
       m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature);
    }
  }
  return m->jmethod_id();
}  

获取Java类中main()方法的jmethod_id。

源代码位置:method.hpp
// Get this method's jmethodID -- allocate if it doesn't exist
jmethodID jmethod_id()  {
	  methodHandle this_h(this);
      return InstanceKlass::get_jmethod_id(method_holder(), this_h);
}

调用的InstanceKlass::get_jmethod_id()函数获取唯一ID,关于如何获取或生成ID的过程这里不再详细介绍,有兴趣的自行研究。

在JavaMain()函数中有如下调用:

mainArgs = CreateApplicationArgs(env, argv, argc);

(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

通过调用CallStaticVoidMethod()函数来调用Java主类中的main()方法。控制权转移到JavaMainClass.main()方法之中,等JavaMainClass.main()方法返回之后当前线程才接手过来清理和关闭HotSpot VM。

源代码位置:openjdk/hotspot/src/share/vm/prims/jni.cpp 

JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
  va_list args;
  va_start(args, methodID);
  JavaValue jvalue(T_VOID);

  JNI_ArgumentPusherVaArg  ap(methodID, args);
  jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
  va_end(args);
JNI_END
  

va_list与va_start是变长参数解析时需要用到的宏。将传给Java方法的参数以C的可变长度参数传入后,使用JNI_ArgumentPusherVaArg实例ap是将其封装起来。JNI_ArgumentPusherVaArg类的继承体系如下:

JNI_ArgumentPusherVaArg->JNI_ArgumentPusher->SignatureIterator

调用的jni_invoke_static()函数的实现如下:

// 通过jni的方式调用Java静态方法
static void jni_invoke_static(
 JNIEnv              *env,
 JavaValue*          result,
 jobject             receiver,
 JNICallType         call_type,
 jmethodID           method_id,
 JNI_ArgumentPusher  *args,
 TRAPS
){
  Method* m = Method::resolve_jmethod_id(method_id);
  methodHandle method(THREAD, m);

  ResourceMark rm(THREAD);
  int number_of_parameters = method->size_of_parameters();
  // 这里进一步将要传给Java的参数转换为JavaCallArguments对象传下去
  JavaCallArguments  java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);

  // Fill out(填,填写) JavaCallArguments object
  Fingerprinter fp =  Fingerprinter(method);
  uint64_t x =  fp.fingerprint();
  args->iterate(x);
  // Initialize result type
  BasicType bt = args->get_ret_type();
  result->set_type(bt);

  // Invoke the method. Result is returned as oop.
  JavaCalls::call(result, method, &java_args, CHECK);

  // Convert result
  if (
    result->get_type() == T_OBJECT || 
    result->get_type() == T_ARRAY
  ) {
     oop tmp =  (oop) result->get_jobject();
     jobject jobj = JNIHandles::make_local(env,tmp);
     result->set_jobject(jobj);
  }
}

通过JavaCalls::call()函数来调用Java主类的main()方法。关于JavaCalls::call()函数大家应该不会陌生,这个函数是怎么建立Java栈帧以及找到Java方法入口在之前详细介绍过。 

推荐阅读:

第1篇-关于JVM运行时,开篇说的简单些

第2篇-JVM虚拟机这样来调用Java主类的main()方法

第3篇-CallStub新栈帧的创建

第4篇-JVM终于开始调用Java主类的main()方法啦

第5篇-调用Java方法后弹出栈帧及处理返回结果

第6篇-Java方法新栈帧的创建

第7篇-为Java方法创建栈帧

第8篇-dispatch_next()函数分派字节码

第9篇-字节码指令的定义

第10篇-初始化模板表

第11篇-认识Stub与StubQueue

第12篇-认识CodeletMark

第13篇-通过InterpreterCodelet存储机器指令片段

第14篇-生成重要的例程

第15章-解释器及解释器生成器

第16章-虚拟机中的汇编器

第17章-x86-64寄存器

第18章-x86指令集之常用指令

第19篇-加载与存储指令(1)

第20篇-加载与存储指令之ldc与_fast_aldc指令(2)

第21篇-加载与存储指令之iload、_fast_iload等(3)

第22篇-虚拟机字节码之运算指令

第23篇-虚拟机字节码指令之类型转换

第24篇-虚拟机对象操作指令之getstatic

第25篇-虚拟机对象操作指令之getfield

第26篇-虚拟机对象操作指令之putstatic

第27篇-虚拟机字节码指令之操作数栈管理指令

第28篇-虚拟机字节码指令之控制转移指令

 

 

  

  

 

 

 

 

 

 

 

 

 

 

  

 

posted on 2021-09-29 15:03  鸠摩(马智)  阅读(1601)  评论(0编辑  收藏  举报

导航