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

其中浅红色的函数由主线程执行,而另外的浅绿色部分由另外一个线程执行,这个线程最终也会负责执行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篇-虚拟机字节码指令之控制转移指令

  

  

  

第29篇-调用Java主类的main()方法的更多相关文章

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

    在前一篇 第3篇-CallStub新栈帧的创建 中我们介绍了generate_call_stub()函数的部分实现,完成了向CallStub栈帧中压入参数的操作,此时的状态如下图所示. 继续看gene ...

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

    在前一篇 第1篇-关于JVM运行时,开篇说的简单些 中介绍了call_static().call_virtual()等函数的作用,这些函数会调用JavaCalls::call()函数.我们看Java类 ...

  3. java 主类的main方法调用其他方法

    方法1:A a=new test().new A(); 内部类对象通过外部类的实例对象调用其内部类构造方法产生,如下: public class test{ class A{ void fA(){ S ...

  4. 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。

    22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...

  5. .编写Java应用程序。首先,定义一个Print类,它有一个方法void output(int x),如果x的值是1,在控制台打印出大写的英文字母表;如果x的值是2,在 控制台打印出小写的英文字母表。其次,再定义一个主类——TestClass,在主类 的main方法中创建Print类的对象,使用这个对象调用方法output ()来打印出大 小写英文字母表。

    package com.homework.zw; //类Print部分 public class Print1 { int x; void output() { if(x==1) { for(int ...

  6. 4.编写Java应用程序。首先,定义一个时钟类——Clock,它包括三个int型 成员变量分别表示时、分、秒,一个构造方法用于对三个成员变量(时、分、秒) 进行初始化,还有一个成员方法show()用于显示时钟对象的时间。其次,再定义 一个主类——TestClass,在主类的main方法中创建多个时钟类的对象,使用这 些对象调用方法show()来显示时钟的时间。

    Clock package com.hanqi.test; public class Clock { int hour,minute,second; Clock(int h,int m,int s) ...

  7. 2.按要求编写Java应用程序: (1)编写西游记人物类(XiYouJiRenWu) 其中属性有:身高(height),名字(name),武器(weapon) 方法有:显示名字(printName),显示武器(printWeapon) (2)在主类的main方法中创建二个对象:zhuBaJie,sunWuKong。并分别为他 们的两个属性(name,weapon)赋值,最后分别调用printNam

    XiYouJiRenWu package com.hanqi.test; public class XiYouJiRenWu { String height,name,weapon; XiYouJiR ...

  8. 7.编写Java应用程序。首先,定义一个Print类,它有一个方法void output(int x),如果x的值是1,在控制台打印出大写的英文字母表;如果x的值是2,在 控制台打印出小写的英文字母表。其次,再定义一个主类——TestClass,在主类 的main方法中创建Print类的对象,使用这个对象调用方法output ()来打印出大 小写英文字母表。

    package com.bao; public class Print1 { int x; void output() { if(x==1) { System.out.println("AB ...

  9. 5.编写Java应用程序。首先,定义描述学生的类——Student,包括学号(int)、 姓名(String)、年龄(int)等属性;二个方法:Student(int stuNo,String name,int age) 用于对对象的初始化,outPut()用于输出学生信息。其次,再定义一个主类—— TestClass,在主类的main方法中创建多个Student类的对象,使用这些对象来测 试St

    Student类: package com.bao; public class Student { int stuNo;String name,sex;int age,weight; Student( ...

随机推荐

  1. Matlab实现BP神经网络预测(附实例数据及代码)

    BP神经网络介绍 神经网络是机器学习中一种常见的数学模型,通过构建类似于大脑神经突触联接的结构,来进行信息处理.在应用神经网络的过程中,处理信息的单元一般分为三类:输入单元.输出单元和隐含单元. 顾名 ...

  2. linux centos7 获取开机时间

    2021-08-03 1. who 命令 who 命令显示关于当前在本地系统上的所有用户信息:登录名,线路,时间,备注 # 列出当前登录本系统的用户 who # 列出本系统的开机/重启时间 who - ...

  3. 安装redis 6.0.6

    1.规划目录:下载目录.安装目录.redis数据目录mkdir -p /data/appmkdir -p /opt/redis_cluster/redis_6379/{conf,logs,pid}mk ...

  4. go语言学习代码

    1.day01 package main //声明文件所在的包,每个go文件必须有归属包 import "fmt" //引入程序中需要用的包,为了使用包下的函数 比如函数:Prin ...

  5. JS_DOM操作之绑定事件

    1 - 静态绑定:直接把事件写在标签元素中 <div id="div" onclick="foo(this)">click</div> ...

  6. GIT:修改上一次提交的注释信息(git commit --amend)

    git commit -m 注释信息 如果这时候注释信息输入错误,就可以输入以下指令更改 git commit --amend 键入" i "进入编辑模式 修改后键入ESC,:wq ...

  7. 一文搞懂Python Unittest测试方法执行顺序

    大家好~我是米洛! 欢迎关注我的公众号测试开发坑货,一起交流!点赞收藏关注,不迷路. Unittest unittest大家应该都不陌生.它作为一款博主在5-6年前最常用的单元测试框架,现在正被pyt ...

  8. python 直接插入排序

    # 先将未排序的元素放到九天之上,一个临时变量temp,上到九天之上去观察前面已经排好的序列, # 然后从后向前对比,只要临时变量小于某个位置的值,就将其向前移动一位,就是给比它下标大 # 1的位置处 ...

  9. P1118 [USACO06FEB]Backward Digit Sums G/S

    P1118 [USACO06FEB]Backward Digit Sums G/S 题解:  (1)暴力法.对1-N这N个数做从小到大的全排列,对每个全排列进行三角形的计算,判断是否等于N.  对每个 ...

  10. Java技术开发专题系列之【Guava RateLimiter】针对于限流器的入门到精通(针对于源码分析介绍)

    Guava包中限流实现分析 RateLimiter 之前的文章中已经介绍了常用的限流算法,而google在Java领域中使用Guava包中的限流工具进行服务限流. 回顾使用案例 Google开源工具包 ...