5.Java程序中python main 函数函数的四要素是什么?

第1页/共23页
Java期末综合练习题
一、选择题。1.B
1.编译Java
Applet 源程序文件产生的字节码文件的扩展名为(
2.main方法是Java
Application程序执行的入口点,关于main方法的方法头以下哪项是合法的(
main( String
C、public static int
main(String
main(String
3.为AB类的一个无形式参数无返回值的方法method书写方法头,使得使用类名AB作为前缀就可以调用它,该方法头的形式为(
D、abstract
4.下列哪个是合法的Java标识符?(
A、Tree&Glasses
B、FirstJavaApplet
C、_$theLastOne
Application源程序的主类是指包含有(
)方法的类。
A、main方法
B、toString方法
C、init方法
D、actionPerfromed方法
10.A、C、D
6.Java的字符类型采用的是Unicode编码方案,每个Unicode码占用(
)个比特位。
7.设 a = 8,则表达式
a &&& 2 的值是(
8.若a的值为3时,下列程序段被执行后,c的值是多少?(
9.在编写Java
Application程序时,若需要使用到标准输入输出语句,必须在程序的开头写上(
java.awt.* ;
java.applet.A
java.io.* ;
java.awt.G
10.对于构造函数,下列叙述正确的是(
A、构造函数是类的一种特殊函数,它的方法名必须与类名相同。
B、构造函数的返回类型只能是void型。
C、构造函数的主要作用是完成对类的对象的初始化工作。
D、一般在创建新对象时,系统会自动调用构造函数。
11.A、C、D
11.下面的是关于类及其修饰符的一些描述,正确的是:(
A、abstract类只能用来派生子类,不能用来创建abstract类的对象。
B、final类不但可以用来派生子类,也可以用来创建final类的对象。
C、abstract不能与final同时修饰一个类。
D、abstract方法必须在abstract类中声明,但abstract类定义中可以没有abstract方法。
12.若需要定义一个类域或类方法,应使用哪种修饰符?(
B、package
C、private
13.若在某一个类定义中定义有如下的方法: abstract
performDial( );该方法属于(
第1页/共23页
寻找更多 ""在java中,main()方法是java应用程序的入口方法。本文是学优网搜索整理的关于的Java main方法面试题10个,特别适合参加Java的朋友阅读,希望对大家有所帮助!想了解更多相关信息请持续关注我们学优考试网!1.不用main方法如何定义一个类?不行,没有main方法我们不能运行Java类。在Java 7之前,你可以通过使用静态初始化运行Java类。但是,从Java 7开始就行不通了。2.main()方法需要的参数不是字符串数组?不是的,main()方法的参数必须是字符串数组。但是,在引进变参时,你可以将字符串类型的变参作为参数传递给main()方法。变参一定得是数组。package com.public class MainMethod{public static void main(String args[]){}}3.我们能不能改变main()方法的返回类型?不能,main()方法的返回类型只能是空。任何其它类型都是不能接受的。package com.public class A{public static int main(String[] args){return 1; //run time error : No main method found}}4.main()方法为什么必须是静态的?main()方法一定是静态的。如果main()允许是非静态的,那么在调用main方法时,JVM就得实例化它的类。在实例化时,还得调用类的构造函数。如果这个类的构造函数有参数,那么届时就会出现歧义。例如,在下面的程序中,在实例化类“A”的时候,JVM传递什么参数?package com.public class A{public MainMethod(int i){//Constructor taking one argument}public void main(String[] args){//main method as non-static}5.我们能不能声明main()方法为非静态?不能,main()方法必须声明为静态的,这样JVM才可以调用main()方法而无需实例化它的类。如果从main()方法去掉“static”这个声明,虽然编译依然可以成功,但在运行时会导致程序失败。package com.public class A{public void main(String[] args){System.out.println(&indhu&);//Run time error}}6.我们能否重载main()方法?可以,我们可以重载main()方法文章出自,转载请保留此链接!。一个Java类可以有任意数量的main()方法。为了运行java类,类的main()方法应该有例如“public static void main(String[] args)”的声明。如果你对此声明做任何修改,编译也是可以成功的。但是,运行不了Java程序。你会得到运行时错误,因为找不到main方法。package com.public class A{public static void main(String[] args){System.out.println(&Indhu&);}void main(int args){System.out.println(&Sindhu&);}long main(int i, long d){System.out.println(&Saidesh&);}}7.我们能否声明main()方法为private或protected,或者不用访问修饰符?不能,main()方法必须public。你不能定义main()方法为private和protected,也不能不用访问修饰符。这是为了能让JVM访问main()方法。如果你不定义main()方法为public,虽然编译也会成功,但你会得到运行时错误,因为找不到main方法package com.public class A{private static void main(String[] args){//Run time error}}8.我们能否在Java中覆盖main方法?不能,你不能在Java中覆盖main方法。这是因为main方法是静态方法,而在Java中静态方法在编译时会结合在一起,所以你在Java中不能覆盖静态方法。9.我们能否在Java中终结main方法?你可以在Java中终结main方法。JVM对此没问题。10.我们能否在Java中同步main方法?是的,main方法可以在Java中同步,synchronized修饰符允许用于main方法的声明中,这样就可以在Java中同步main方法了。最近更新:免责声明:本文仅代表作者个人观点,与本网无关。看完本文,记得打分哦:很好下载Doc格式文档马上分享给朋友:?知道苹果代表什么吗实用文章,深受网友追捧比较有用,值得网友借鉴没有价值,写作仍需努力相关网页程序设计:
48小时热门java中的main函数中args[]参数解析
args[] 在命令行运行时候输入的参数,因为参数可以为多个,所以要用数组来存,比如dos的copy命令:
  copy c:\*.* d:\*.*
  这就是两个参数c:\*.*和d:\*.*
  java在命令行运行的时候要用到java命令:
&  java Test value1 value2
  后面就是两个参数,在main里面args[]就是两个长度的数组value1存在args[1]中,value2存在args[2]中。
&  args[0]存储的是命令,其后才是参数!
&  在Java中有这样一条语句:public static void
main(String[] args)。其中的args是Java命令行参数,我们在DOS中执行Java程序的时候使用“java 文件名
args参数”。args这个数组可以接收到这些参数。
  main(argc,argv)
&  int argc,char *argv[];
&  {   .....   }
&  从函数参数的形式上看,包含一个整型和一个指针数组。当一个C的源程序经过编译、链接后,会生成扩展名为.EXE的可执行文件,这是可以在操作系统下直接运行的文件,换句话说,就是由系统来启动运行的。对main()函数既然不能由其它函数调用和传递参数,就只能由系统在启动运行时传递参数了。
&  在操作系统环境下,一条完整的运行命令应包括两部分:命令与相应的参数。其格式为:
&  命令参数1参数2....参数n&
&  此格式也称为命令行。命令行中的命令就是可执行文件的文件名,其后所跟参数需用空格分隔,并为对命令的进一步补充,也即是传递给main()函数的参数。
&  命令行与main()函数的参数存在如下的关系:
&  设命令行为:program str1 str2 str3 str4
&  其中program为文件名,也就是一个由program.c经编译、链接后生成的可执行文件
program.exe,其后各跟5个参数。对main()函数来说,它的参数argc记录了命令行中命令与参数的个数,共6个,指针数组的大小由参数
argc的值决定,即为char*argv[6],指针数组的取值情况如图6-15所示。
&  数组的各指针分别指向一个字符串。应当引起注意的是接收到的指针数组的各指针是从命令行的开始接收的,首先接收到的是命令,其后才是参数。
直接调用:
当然也可以在一个类中main方法中直接调用另一个类里的main方法,因为main方法都是static修饰的静态方法,因此可以通过类名.main来调用,这时就可在调用处main方法中传入String[]类型的字符串数组,达到调用的目的,也可不传入参数。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。Java Main如何被执行? - ImportNew
| 标签: ,
  java应用程序的启动在在/hotspot/src/share/tools/launcher/java.c的main()函数中,而在虚拟机初始化过程中,将创建并启动Java的Main线程。最后将调用JNIEnv的CallStaticVoidMethod()来执行main方法。
CallStaticVoidMethod()对应的jni函数为jni_CallStaticVoidMethod,定义在/hotspot/src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod()又调用了jni_invoke_static(),jni_invoke_static()通过JavaCalls的call()发起对Java方法的调用
所有来自虚拟机对Java函数的调用最终都将由JavaCalls模块来完成,JavaCalls将通过call_helper()来执行Java方法并返回调用结果,并最终调用StubRoutines::call_stub()来执行Java方法:
// do call
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread);
// HandleMark used by HandleMarkCleaner
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result-&_value), // see NOTE above (compiler problem)
result_val_address,
// see NOTE above (compiler problem)
result_type,
entry_point,
args-&parameters(),
args-&size_of_parameters(),
result = link.result();
// circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
// Preserve oop return value across possible gc points
if (oop_result_flag) {
thread-&set_vm_result((oop) result-&get_jobject());
call_stub()定义在/hotspot/src/share/vm/runtime/stubRoutines.h中,实际上返回的就 是CallStub函数指针_call_stub_entry,该指针指向call_stub的汇编实现的目标代码指令地址,即call_stub的例程 入口。
// Calls to Java
typedef void (*CallStub)(
intptr_t* result,
BasicType result_type,
methodOopDesc* method,
entry_point,
intptr_t* parameters,
size_of_parameters,
static CallStub call_stub()
{ return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }
在分析call_stub的汇编代码之前,先了解下x86寄存器和栈帧以及函数调用的相关知识。
x86-64的所有寄存器都是与 机器字长(数据总线位宽)相同,即64位的,x86-64将x86的8个32位通用寄存器扩展为64位(eax、ebx、ecx、edx、eci、 edi、ebp、esp),并且增加了8个新的64位寄存器(r8-r15),在命名方式上,也从”exx”变为”rxx”,但仍保留”exx”进行32 位操作,下表描述了各寄存器的命名和作用
此外,还有16个128位的XMM寄存器,分别为xmm0-15,x84-64的寄存器遵循调用约定(Calling Conventions):
1.参数传递:
(1).前4个参数的int类型分别通过rcx、rdx、r8、r9传递,多余的在栈空间上传递(从右向左依次入栈),寄存器所有的参数都是向右对齐的(低位对齐)
(2).浮点数类型的参数通过xmm0-xmm3传递,注意不同类型的参数占用的寄存器序号是根据参数的序号来决定的,比如add(int,double,float,int)就分别保存在rcx、xmm1、xmm2、r9寄存器中
(3).8/16/32/64类型的结构体或共用体和_m64类型将使用rcx、rdx、r8、r9直接传递,而其他类型将会通过指针引用的方式在这4个寄存器中传递
(4).被调用函数当需要时要把寄存器中的参数移动到栈空间中(shadow space)
2.返回值传递
(1).对于可以填充为64位的返回值(包括_m64)将使用rax进行传递
(2).对于_m128(i/d)以及浮点数类型将使用xmm0传递
(3).对于64位以上的返回值,将由调用函数在栈上为其分配空间,并将其指针保存在rcx中作为”第一个参数”,而传入参数将依次右移,最后函数调用完后,由rax返回该空间的指针
(4).用户定义的返回值类型长度必须是1、2、4、8、16、32、64
3.调用者/被调用者保存寄存器
调用者保存寄存器:rax、rcx、rdx、r8-r11都认为是易失型寄存器(volatile),这些寄存器随时可能被用到,这些寄存器将由调用者 自行维护,当调用其他函数时,被调用函数对这些寄存器的操作并不会影响调用函数(即这些寄存器的作用范围仅限于当前函数)。
被调用者保存寄 存器:rbx、rbp、rdi、rsi、r12-r15、xmm6-xmm15都是非易失型寄存器(non-volatile),调用其他函数时,这些寄 存器的值可能在调用返回时还需要用,那么被调用函数就必须将这些寄存器的值保存起来,当要返回时,恢复这些寄存器的值(即这些寄存器的作用范围是跨函数调 用的)。
以如下程序为例,分析函数调用的栈帧布局:
double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2)
int local_i1, local_i2;
float local_f1;
double local_d1;
double local_d2 = 3.0;
local_i1 = param_i1;
local_i2 = param_i2;
local_f1 = param_f1;
local_d1 = param_d1;
return local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2;
int main()
res = func(1, 1.0, 2.0, 3, 3.0);
main函数调用func之前的汇编代码如下:
.seh_pushreg
%rsp, %rbp
//更新栈基址
.seh_setframe
.seh_stackalloc 80
//main栈需要80字节的栈空间
.seh_endprologue
movabsq $7387904, %rdx //0x0000,即浮点数2.0
movabsq $1073152, %rax //0x0000,即浮点数3.0
%rax, 32(%rsp)
//第5个参数3.0,即param_d2保存在栈空间上
//第4个参数3,即param_i2保存在r9d中(r9的低32位)
%rdx, -24(%rbp)
-24(%rbp), %xmm2
//第3个参数2.0,即param_d1保存在xmm2中
.LC2(%rip), %xmm1
//第2个参数1.0(0x3f800000),保存在xmm1中
//第1个参数1,保存在ecx中(rcx的低32位)
func函数返回后,main函数将从xmm0中取出返回结果
%xmm0, %rax
//保存结果
%rax, -8(%rbp)
//清空eax,回收main栈,恢复栈顶地址
func函数的栈和操作数准备如下:
//保存rbp(main函数栈的基址)
.seh_pushreg
%rsp, %rbp
//将main栈的栈顶指针作为被调用函数的栈基址
.seh_setframe
//func栈需要32字节的栈空间
.seh_stackalloc 32
.seh_endprologue
%ecx, 16(%rbp)
//将4个参数移动到栈底偏移16-40的空间(main栈的shadow space)
%xmm1, 24(%rbp)
%xmm2, 32(%rbp)
%r9d, 40(%rbp)
movabsq $1073152, %rax //本地变量local_d2,即浮点数3.0
%rax, -8(%rbp)
//5个局部变量
16(%rbp), %eax
%eax, -12(%rbp)
40(%rbp), %eax
%eax, -16(%rbp)
24(%rbp), %eax
%eax, -20(%rbp)
32(%rbp), %rax
%rax, -32(%rbp)
随后的func的运算过程如下:
-16(%rbp), %eax //local_i2 - local_i1
-12(%rbp), %eax
%xmm0, %xmm0
//准备xmm0寄存器,按位异或,xmm0清零
%eax, %xmm0
-20(%rbp), %xmm0
//local_f1 * (local_i2 - local_i1)
%xmm0, %xmm0
-32(%rbp), %xmm0
//local_d1 + local_f1 * (local_i2 - local_i1)
48(%rbp), %xmm0
//local_d1 + local_f1 * (local_i2 - local_i1) - param_d2
-8(%rbp), %xmm0
//local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2
//回收func栈,恢复栈顶地址
根据以上代码分析,大概得出该程序调用栈结构:
这里没有考虑func函数再次调用其他函数而准备操作数的栈内容的情况,但结合main函数栈,大致可以得出栈的通用结构如下:
call_stub由generate_call_stub()解释成汇编代码,有兴趣的可以继续阅读call_stub的汇编代码进行分析。
下面对call_stub的汇编部分进行分析:
先来看下call_stub的调用栈结构:(注:本文实验是在windows_64位平台上实现的)
// Call stubs are used to call Java from C
return_from_Java 是紧跟在call *%eax后面的那条指令的地址
[ return_from_Java
] &--- rsp
// -28 [ arguments
] &-- rbp - 0xe8
// -26 [ saved xmm15
] &-- rbp - 0xd8
// -24 [ saved xmm14
] &-- rbp - 0xc8
// -22 [ saved xmm13
] &-- rbp - 0xb8
// -20 [ saved xmm12
] &-- rbp - 0xa8
// -18 [ saved xmm11
] &-- rbp - 0x98
// -16 [ saved xmm10
] &-- rbp - 0x88
// -14 [ saved xmm9
] &-- rbp - 0x78
// -12 [ saved xmm8
] &-- rbp - 0x68
// -10 [ saved xmm7
] &-- rbp - 0x58
[ saved xmm6
] &-- rbp - 0x48
[ saved r15
] &-- rbp - 0x38
[ saved r14
] &-- rbp - 0x30
[ saved r13
] &-- rbp - 0x28
[ saved r12
] &-- rbp - 0x20
[ saved rdi
] &-- rbp - 0x18
[ saved rsi
] &-- rbp - 0x10
[ saved rbx
] &-- rbp - 0x8
[ saved rbp
] &--- rbp,
1 [ return address
&--- rbp + 0x08
2 [ ptr. to call wrapper ]
&--- rbp + 0x10
3 [ result
&--- rbp + 0x18
4 [ result_type
&--- rbp + 0x20
5 [ method
&--- rbp + 0x28
6 [ entry_point
&--- rbp + 0x30
7 [ parameters
&--- rbp + 0x38
8 [ parameter_size
&--- rbp + 0x40
9 [ thread
&--- rbp + 0x48
1.根据函数调用栈的结构:
在被调函数栈帧的栈底 %rbp + 8(栈地址向下增长,堆地址向上增长,栈底的正偏移值指向调用函数栈帧内容)保存着被调函数的传入参数,这里即:
JavaCallWrapper指针、返回结果指针、返回结果类型、被调用方法的methodOop、被调用方法的解释代码的入口地址、参数地址、参数个数。
StubRoutines::call_stub [0xcb[ (356 bytes)
0x0567: push
//更新栈顶地址
0x0568: mov
//call_stub需要的栈空间大小为0xd8
0x056b: sub
$0xd8,%rsp
2.rcx、rdx、r8d、r9d分别保存着传入call_stub的前4个参数,现在需要将其复制到栈上的shadow space中
//分别使用rcx、rdx、r8、r9来保存第1、2、3、4个参数,多出来的其他参数用栈空间来传递
//使用xmm0-4来传递第1-4个浮点数参数
//这里将参数复制到栈空间,这样call_stub的所有参数就在rbp + 0x10 ~ 0x48栈空间上
0x0572: mov
%r9,0x28(%rbp)
0x0576: mov
%r8d,0x20(%rbp)
0x057a: mov
%rdx,0x18(%rbp)
0x057e: mov
%rcx,0x10(%rbp)
3.将被调用者保存寄存器的值压入call_stub栈中:
;; save registers:
//依次保存rbx、rsi、rdi这三个被调用者保存的寄存器,随后保存r12-r15、XMM寄存器组xmm6-xmm15
0x0582: mov
%rbx,-0x8(%rbp)
0x0586: mov
%r12,-0x20(%rbp)
0x058a: mov
%r13,-0x28(%rbp)
0x058e: mov
%r14,-0x30(%rbp)
0x0592: mov
%r15,-0x38(%rbp)
0x0596: vmovdqu %xmm6,-0x48(%rbp)
0x059b: vmovdqu %xmm7,-0x58(%rbp)
0x05a0: vmovdqu %xmm8,-0x68(%rbp)
0x05a5: vmovdqu %xmm9,-0x78(%rbp)
0x05aa: vmovdqu %xmm10,-0x88(%rbp)
0x05b2: vmovdqu %xmm11,-0x98(%rbp)
0x05ba: vmovdqu %xmm12,-0xa8(%rbp)
0x05c2: vmovdqu %xmm13,-0xb8(%rbp)
0x05ca: vmovdqu %xmm14,-0xc8(%rbp)
0x05d2: vmovdqu %xmm15,-0xd8(%rbp)
0x05da: mov
%rsi,-0x10(%rbp)
0x05de: mov
%rdi,-0x18(%rbp)
//栈底指针的0x48偏移保存着thread对象,0x6d01a2c3(%rip)为异常处理入口
0x05e2: mov
0x48(%rbp),%r15
0x05e6: mov
0x6d01a2c3(%rip),%r12
4.call_stub的参数保存着Java方法的参数,现在就需要将参数压入call_stub栈中
/栈底指针的0x40偏移保存着参数的个数
0x05ed: mov
0x40(%rbp),%r9d
//若参数个数为0,则直接跳转0x060d准备调用Java方法
0x05f1: test
0x05f4: je
//若参数个数不为0,则遍历参数,将所有参数压入本地栈
//其中栈底指针的0x38偏移保存着参数的地址,edx将用作循环的迭代器
0x05fa: mov
0x38(%rbp),%r8
0x05fe: mov
//从第一个参数开始,将Java方法的参数压人本地栈
i = parameter_ //确保不等于0
push(parameter[i]);
}while(i!=0);
0x0601: mov
(%r8),%rax
0x0604: add
0x0608: dec
0x060a: push
0x060b: jne
5.调用Java方法的解释代码
;; prepare entry:
//栈底指针的0x28和0x30偏移分别保存着被调用Java方法的methodOop指针和解释代码的入口地址
0x060d: mov
0x28(%rbp),%rbx
0x0611: mov
0x30(%rbp),%rdx
0x0615: mov
//保存栈顶指针
; jump to run Java method:
0x0618: callq
6.准备保存返回结果,这里需要先根据不同的返回类型取出返回结果,然后保存到返回结果指针所指向的位置
;; prepare to save result:
//栈底指针的0x18和0x20偏移分别保存着返回结果的指针和结果类型
0x061a: mov
0x18(%rbp),%rcx
0x061e: mov
0x20(%rbp),%edx
; handle result accord to different result_type:
0x0621: cmp
0x0624: je
0x062a: cmp
0x062d: je
0x0633: cmp
0x0636: je
0x063c: cmp
0x063f: je
; save result for the other result_type:
0x0645: mov
%eax,(%rcx)
下面分别为返回结果类型为long、float、double的情况
;; long 类型返回结果保存:
0x06b7: mov
%rax,(%rcx)
0x06ba: jmp
; float 类型返回结果保存:
0x06bc: vmovss %xmm0,(%rcx)
0x06c0: jmp
; double 类型返回结果保存:
0x06c2: vmovsd %xmm0,(%rcx)
0x06c6: jmpq
7.被调用者保存寄存器的恢复,以及栈指针的复位
;; restore registers:
0x0647: lea
-0xd8(%rbp),%rsp
0x064e: vmovdqu -0xd8(%rbp),%xmm15
0x0656: vmovdqu -0xc8(%rbp),%xmm14
0x065e: vmovdqu -0xb8(%rbp),%xmm13
0x0666: vmovdqu -0xa8(%rbp),%xmm12
0x066e: vmovdqu -0x98(%rbp),%xmm11
0x0676: vmovdqu -0x88(%rbp),%xmm10
0x067e: vmovdqu -0x78(%rbp),%xmm9
0x0683: vmovdqu -0x68(%rbp),%xmm8
0x0688: vmovdqu -0x58(%rbp),%xmm7
0x068d: vmovdqu -0x48(%rbp),%xmm6
0x0692: mov
-0x38(%rbp),%r15
0x0696: mov
-0x30(%rbp),%r14
0x069a: mov
-0x28(%rbp),%r13
0x069e: mov
-0x20(%rbp),%r12
0x06a2: mov
-0x8(%rbp),%rbx
0x06a6: mov
-0x18(%rbp),%rdi
0x06aa: mov
-0x10(%rbp),%rsi
; back to old(caller) stack frame:
0x06ae: add
$0xd8,%rsp //栈顶指针复位
0x06b5: pop
%rbp //栈底指针复位
0x06b6: retq
归纳出call_stub栈结构如下:
8.对于不同的Java方法,虚拟机在初始化时会生成不同的方法入口例程
(method entry point)来准备栈帧,这里以较常被使用的zerolocals方法入口为例,分析Java方法的栈帧结构与调用过程,入口例程目标代码的产生在InterpreterGenerator::generate_normal_entry()中:
(1).根据之前的分析,初始的栈结构如下:
获取传入参数数量到rcx中:
address InterpreterGenerator::generate_normal_entry(bool synchronized) {
// determine code generation flags
bool inc_counter
= UseCompiler || CountCompiledC
// ebx: methodOop
// r13: sender sp
address entry_point = __ pc();
const Address size_of_parameters(rbx,
methodOopDesc::size_of_parameters_offset());
const Address size_of_locals(rbx, methodOopDesc::size_of_locals_offset());
const Address invocation_counter(rbx,
methodOopDesc::invocation_counter_offset() +
InvocationCounter::counter_offset());
const Address access_flags(rbx, methodOopDesc::access_flags_offset());
// get parameter size (always needed)
__ load_unsigned_short(rcx, size_of_parameters);
其中methodOop指针被保存在rbx中,调用Java方法的sender sp被保存在r13中,参数大小保存在rcx中
(2). 获取局部变量区的大小,保存在rdx中,并减去参数数量,将除参数以外的局部变量数量保存在rdx中(虽然参数作为局部变量是方法的一部分,但参数由调用 者提供,这些参数应有调用者栈帧而非被调用者栈帧维护,即被调用者栈帧只需要维护局部变量中除了参数的部分即可)
// rbx: methodOop
// rcx: size of parameters
// r13: sender_sp (could differ from sp+wordSize if we were called via c2i )
__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
__ subl(rdx, rcx); // rdx = no. of additional locals
(3).对栈空间大小进行检查,判断是否会发生栈溢出
// see if we've got enough room on the stack for locals plus overhead.
generate_stack_overflow_check();
(4).获取返回地址,保存在rax中(注意此时栈顶为调用函数call指令后下一条指令的地址)
// get return address
__ pop(rax);
(5).由于参数在栈中由低地址向高地址是以相反的顺序存放的,所以第一个参数的地址应该是 rsp+rcx*8-8(第一个参数地址范围为 rsp+rcx*8-8 ~ rsp+rcx*8),将其保存在r14中
// compute beginning of parameters (r14)
__ lea(r14, Address(rsp, rcx, Address::times_8, -wordSize))
(6).为除参数以外的局部变量分配栈空间,若这些局部变量数量为0,那么就跳过这一部分处理,否则,将压入 maxlocals – param_size个0,以初始化这些局部变量
//该部分为一个loop循环
// rdx - # of additional locals
// allocate space for locals
// explicitly initialize locals
Label exit,
__ testl(rdx, rdx);
__ jcc(Assembler::lessEqual, exit); // do nothing if rdx &= 0
__ bind(loop);
__ push((int) NULL_WORD); // initialize local variables
__ decrementl(rdx); // until everything initialized
__ jcc(Assembler::greater, loop);
__ bind(exit);
这时栈的层次如下:
(7).将方法的调用次数保存在rcx/ecx中
// (pre-)fetch invocation count
if (inc_counter) {
__ movl(rcx, invocation_counter);
(8).初始化当前方法的栈帧
// initialize fixed part of activation frame
generate_fixed_frame(false);
generate_fixed_frame()的实现如下:
__ push(rax);
// save return address
__ enter();
// save old & set new rbp
__ push(r13);
// set sender sp
__ push((int)NULL_WORD); // leave last_sp as null
__ movptr(r13, Address(rbx, methodOopDesc::const_offset()));
// get constMethodOop
__ lea(r13, Address(r13, constMethodOopDesc::codes_offset())); // get codebase
__ push(rbx);
保存返回地址,为被调用的Java方法准备栈帧,并将sender sp指针、last_sp(设置为0)压入栈,根据methodOop的constMethodOop成员将字节码指针保存到r13寄存器中,并将methodOop压入栈
__ push(0); //methodData
__ movptr(rdx, Address(rbx, methodOopDesc::constants_offset()));
__ movptr(rdx, Address(rdx, constantPoolOopDesc::cache_offset_in_bytes()));
__ push(rdx); // set constant pool cache
__ push(r14); // set locals pointer
if (native_call) {
__ push(0); // no bcp
__ push(r13); // set bcp
__ push(0); // reserve word for pointer to expression stack bottom
__ movptr(Address(rsp, 0), rsp); // set expression stack bottom
将methodData以0为初始值压入栈,根据methodOop的ConstantPoolOop成员将常量池缓冲地址压入栈,r14中保存着 局部变量区(第一个参数的地址)指针,将其压入栈,此外如果调用的是native调用,那么字节码指针部分为0,否则正常将字节码指针压入栈,最后为栈留 出一个字的表达式栈底空间,并更新rsp
最后栈的空间结构如下:
(9).增加方法的调用计数
// increment invocation count & check for overflow
Label invocation_counter_
Label profile_
Label profile_method_
if (inc_counter) {
generate_counter_incr(&invocation_counter_overflow,
&profile_method,
&profile_method_continue);
if (ProfileInterpreter) {
__ bind(profile_method_continue);
(当调用深度过大会抛出StackOverFlow异常)
(10).同步方法的Monitor对象分配和方法的加锁(在汇编部分分析中没有该部分,如果对同步感兴趣的请自行分析)
if (synchronized) {
// Allocate monitor and lock method
lock_method();
(11).JVM工具接口部分
// jvmti support
__ notify_method_entry();
(12).跳转到第一条字节码的本地代码处执行
__ dispatch_next(vtos);
以上分析可能略显复杂,但重要的是明白方法的入口例程是如何为Java方法构造新的栈帧,从而为字节码的运行提供调用栈环境。
method entry point汇编代码的分析可以参考随后的一篇文章。
欢迎各位一起学习 学习群里有相关面试资料 前面625中间783后面520
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew}

我要回帖

更多关于 java main函数 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信