您的位置  > 互联网

Java程序运行时,必须经过编译和运行两个步骤

Java 是如何工作的

Java中引入了虚拟机的概念,在机器和编译器之间添加了虚拟机的抽象层。 该虚拟机为任何平台上的编译器提供了通用接口。 编译器只需要面向虚拟机,生成虚拟机可以理解的代码,然后解释器将虚拟机代码转换为特定系统的机器代码来执行。 在Java中,这种供虚拟机理解的代码称为字节码(),它不面向任何特定处理器,只面向虚拟机。 每个平台的解释器不一样,但是实现的虚拟机是一样的。 Java源程序经过编译器编译后成为字节码。 字节码由虚拟机解释并执行。 虚拟机将每个要执行的字节码发送给解释器,解释器将其翻译成特定机器上的机器码。 代码,然后在特定机器上运行它。

Java代码编译执行的全过程

Java代码的编译是由Java源代码编译器完成的。 流程图如下:

Java字节码的执行是由JVM执行引擎完成的。 流程图如下:

Java代码编译和执行的整个过程包括以下三个重要机制:

Java源码编译机制

类加载机制

类执行机制

Java源码编译机制

Java源代码编译由以下三个过程组成:(javac - 输出有关编译器正在做什么的消息)

解析并导入符号表

注释处理

语义分析和类文件生成

最终生成的class文件由以下部分组成:

1.结构信息。包括类文件格式版本号以及各部分的数量和大小信息

2. 元数据。 对应Java源代码中的声明和常量信息。包含类/继承超类/实现接口的声明信息、域和方法声明信息以及常量池

3.方法信息。 对应Java源代码中语句和表达式对应的信息。包含字节码、异常处理表、求值栈和局部变量区大小、求值栈类型记录、调试符号信息

类加载机制

JVM的类加载是通过其子类完成的。 类的层次关系和加载顺序可以用下图来描述:

1) /启动类加载器

$ 中的 jre/lib/rt.jar 中的所有类都是用 C++ 实现的,并且不是子类。

2) /扩展类加载器

负责加载java平台中一些具有扩展功能的jar包,包括$中的jre/lib/*.jar或者-Djava.ext.dirs指定的目录下的jar包

3)应用程序/系统类加载器

负责记录指定目录下的jar包和class

4) /用户定义的类加载器(java.lang的子类。)

它是由应用程序根据自己的需求定制的。 例如jboss就会按照j2ee规范来实现。

在加载过程中,会首先检查类是否已经加载。 检查顺序为从下到上、逐层检查。 只要有一个类被加载,就视为已加载,保证所有类只加载一次。 加载顺序是从上到下,即上层尝试逐层加载该类。

类加载双亲委托机制介绍与分析

这里需要注意的是,JVM在加载类时默认使用双亲委派机制。 通俗地说,当特定的类加载器收到加载类的请求时,它首先将加载任务委托给父类加载器,然后递归。 如果父类加载器能够完成类加载任务,则成功返回; 只有当父类加载器无法完成加载任务时,才会自行加载。

类执行机制

JVM基于堆栈架构来执行类字节码。 线程创建后,会产生一个程序计数器(PC)和一个堆栈(Stack)。 程序计数器存储该方法内要执行的下一条指令的偏移量。 栈中存储的是栈帧,每个栈帧对应于每次调用一个方法时,栈帧由两部分组成:局部变量区和操作数栈。 局部变量区用于存储方法中的局部变量和参数,操作数栈用于存储方法执行过程中产生的数据。 中间结果。

执行流程如下

1.为main方法创建栈帧:

局部变量表长度为2,slot0存储参数args,slot1存储局部变量s,操作数栈最大深度为5。

2. new#7指令在Java堆中创建一个对象,并将其引用值放在堆栈顶部。

3.初始化一个对象(通过实例构造)

up指令:复制栈顶的值,然后将复制的结果压入堆栈。

23:将单字节常量值23压入堆栈。

ldc #8:取出常量池#8中的常量“”,压入堆栈。

ldc #9:取出常量池#9中的常量“”,压入堆栈。

4、#10:调用常量#10代表的方法,即.()方法。 这一步是初始化对象s的值。

init()方法是编译器将调用父类的init()的语句、构造代码块、实例字段赋值语句以及自己编写的构造方法中的语句集成在一起生成的方法。 确保开头调用的是父类的“init”()方法,最后是自己写的构造函数语句,构造代码块和实例字段赋值语句都集成到了“init”()方法中出现的顺序。

注意“init”()方法的最大操作数栈深度为3,局部变量表大小为4。

此时请注意:从 dup 到 ldc #9 的 4 条指令向堆栈添加了 4 个数据,并且 .() 方法也需要 4 个参数:

(int 年龄、姓名、sid) {

超级(年龄,姓名);

this.sid = sid;

}

虽然定义中只显式定义了传入三个参数,但实际上隐式传入了当前对象的引用作为第一个参数,所以四个参数依次是this、age、name、sid。

上面4条指令只是将这4个参数的值依次压入栈,传递参数,然后调用“init”()方法,该方法会创建该方法的栈帧并压入栈。 栈帧中局部变量表的槽位0到4分别存储压入栈的四个参数值。

创建“init”()方法的堆栈帧:

.init()方法中的字节码指令:

:将局部变量表slot0处的引用值压入栈

:将局部变量表slot1处的int值压入栈

:将局部变量表slot2处的引用值压入栈

#1:调用.()方法,与调用.()方法类似。 过程。 它创建了一个栈帧,将三个参数的值存储在局部变量表中等等,这里就不画图了……

从.()返回后,栈顶用于传递参数的三个值被回收。

:将slot0处的参考值压入堆栈。

:将slot3处的参考值压入堆栈。

#2:将当前栈顶的值“”赋给引用对象的sid字段,然后将这两个值弹出栈中。

:返回调用者,main()方法,并弹出当前方法栈帧。

返回main()方法,继续执行以下字节码指令:

:将栈顶当前引用类型的值赋给slot1处的局部变量,然后弹出栈。

5、至此,第一行代码已经执行完毕,s返回到局部变量表,接下来执行下面的代码。

无效主([]参数){

s = new(23,“”,“”); // 执行完成

s.研究(5,6);

.();

s.run();

}11

:将slot1处的引用类型值压入栈中

:将常量5压入堆栈。 只有0-5个int类型常量有对应的指令。

6:将常量6压入堆栈

6.开始执行第二行代码,也就是方法

#11:调用虚方法study()。 该方法是重写接口中的方法,需要动态调度,所以使用指令。

为 Study() 方法创建一个堆栈帧:

最大堆栈深度3,局部变量表5

该方法的java源代码:

在这里编写代码片段 int Study(int a, int b) {

整数 c = 10;

整数d=20;

a+b*cd;

}

10:将10压入堆栈

:将栈顶的值10赋值给slot3处的int局部变量,即c,并出栈。

20:将20压入堆栈

4:将栈顶的20付给槽4处的int局部变量,即d,并出栈。

以上4条指令完成了c和d的赋值。

,, 这三个指令将三个局部变量 slot1、slot2 和 slot3 压入堆栈:

imul:将栈顶的两个值弹出,并将相乘结果压入栈:

iadd:弹出当前栈顶的两个值,并将相加结果压入栈

iload 4:将slot4处的int型局部变量放入

isub:将栈顶的两个值弹出,并将相减结果压入栈:

:将栈顶的当前值返回给调用者。

7、至此,第二行代码已经执行完毕,将返回值返回给s,执行下面的内容。

无效主([]参数){

s = new(23,“”,“”); // 执行完成

s.研究(5,6);

.();

s.run();

}11

#12 调用静态方法()不需要传递任何参数

pop:()方法有返回值,将其从堆栈中弹出

:将slot1处的参考值压入堆栈

#13:调用对象的run()方法,该方法重写了父类的方法,需要动态调度,所以使用指令

:main() 返回,程序结束。

Java程序运行时,必须经过两个步骤:编译和执行。 首先编译后缀名为.java的源文件,最后生成后缀名为.class的字节码文件。 然后Java虚拟机将编译好的字节码文件加载到内存中(这个过程称为类加载,由加载器完成),然后虚拟机解释执行加载到内存中的Java类并显示结果。

Java 是如何工作的

Java中引入了虚拟机的概念,在机器和编译器之间添加了虚拟机的抽象层。 该虚拟机为任何平台上的编译器提供了通用接口。 编译器只需要面向虚拟机,生成虚拟机可以理解的代码,然后解释器将虚拟机代码转换为特定系统的机器代码来执行。 在Java中,这种供虚拟机理解的代码称为字节码(),它不面向任何特定处理器,只面向虚拟机。 每个平台的解释器不一样,但是实现的虚拟机是一样的。 Java源程序经过编译器编译后成为字节码。 字节码由虚拟机解释并执行。 虚拟机将每个要执行的字节码发送给解释器,解释器将其翻译成特定机器上的机器码。 代码,然后在特定机器上运行它。

Java代码编译执行的全过程

Java代码的编译是由Java源代码编译器完成的。 流程图如下:

Java字节码的执行是由JVM执行引擎完成的。 流程图如下:

Java代码编译和执行的整个过程包括以下三个重要机制:

Java源码编译机制

类加载机制

类执行机制

Java源码编译机制

Java源代码编译由以下三个过程组成:(javac – 输出有关编译器正在做什么的消息)

解析并导入符号表

注释处理

语义分析和类文件生成

最终生成的class文件由以下部分组成:

1.结构信息。包括类文件格式版本号以及各部分的数量和大小信息

2. 元数据。 对应Java源代码中的声明和常量信息。包含类/继承超类/实现接口的声明信息、域和方法声明信息以及常量池

3.方法信息。 对应Java源代码中语句和表达式对应的信息。包含字节码、异常处理表、求值栈和局部变量区大小、求值栈类型记录、调试符号信息

类加载机制

JVM的类加载是通过其子类完成的。 类的层次关系和加载顺序可以用下图来描述:

1) /启动类加载器

$ 中的 jre/lib/rt.jar 中的所有类都是用 C++ 实现的,并且不是子类。

2) /扩展类加载器

负责加载java平台中一些具有扩展功能的jar包,包括$中的jre/lib/*.jar或者-Djava.ext.dirs指定的目录下的jar包

3)应用程序/系统类加载器

负责记录指定目录下的jar包和class

4) /用户定义的类加载器(java.lang的子类。)

它是由应用程序根据自己的需求定制的。 例如jboss就会按照j2ee规范来实现。

在加载过程中,会首先检查类是否已经加载。 检查顺序为从下到上、逐层检查。 只要某个类被加载,就视为已加载,保证所有类只加载一次。 加载顺序是从上到下,即上层尝试逐层加载该类。

类加载双亲委托机制介绍与分析

这里需要注意的是,JVM在加载类时默认使用双亲委派机制。 通俗地说,当特定的类加载器收到加载类的请求时,它首先将加载任务委托给父类加载器,然后递归。 如果父类加载器能够完成类加载任务,则成功返回; 只有当父类加载器无法完成加载任务时,才会自行加载。

类执行机制

JVM基于堆栈架构来执行类字节码。 线程创建后,会产生一个程序计数器(PC)和一个堆栈(Stack)。 程序计数器存储该方法内要执行的下一条指令的偏移量。 栈中存储的是栈帧,每个栈帧对应于每次调用一个方法时,栈帧由两部分组成:局部变量区和操作数栈。 局部变量区用于存储方法中的局部变量和参数,操作数栈用于存储方法执行过程中产生的数据。 中间结果。

执行流程如下

1.为main方法创建栈帧:

局部变量表长度为2,slot0存储参数args,slot1存储局部变量s,操作数栈最大深度为5。

2. new#7指令在Java堆中创建一个对象,并将其引用值放在堆栈顶部。

3.初始化一个对象(通过实例构造)

up指令:复制栈顶的值,然后将复制的结果压入堆栈。

23:将单字节常量值23压入堆栈。

ldc #8:取出常量池#8中的常量“”,压入堆栈。

ldc #9:取出常量池#9中的常量“”,压入堆栈。

4、#10:调用常量#10代表的方法,即.()方法。 这一步是初始化对象s的值。

init()方法是编译器将调用父类的init()的语句、构造代码块、实例字段赋值语句以及自己编写的构造方法中的语句集成在一起生成的方法。 确保开头调用的是父类的“init”()方法,最后是自己写的构造函数语句,构造代码块和实例字段赋值语句都集成到了“init”()方法中出现的顺序。

注意“init”()方法的最大操作数栈深度为3,局部变量表大小为4。

此时请注意:从 dup 到 ldc #9 的 4 条指令向堆栈添加了 4 个数据,并且 .() 方法也需要 4 个参数:

(int 年龄、姓名、sid) {

超级(年龄,姓名);

this.sid = sid;

}

虽然定义中只显式定义了传入三个参数,但实际上隐式传入了当前对象的引用作为第一个参数,所以四个参数依次是this、age、name、sid。

上面4条指令只是将这4个参数的值依次压入栈,传递参数,然后调用“init”()方法,该方法会创建该方法的栈帧并压入栈。 栈帧中局部变量表的槽位0到4分别存储压入栈的四个参数值。

创建“init”()方法的堆栈帧:

.init()方法中的字节码指令:

:将局部变量表slot0处的引用值压入栈

:将局部变量表slot1处的int值压入栈

:将局部变量表slot2处的引用值压入栈

#1:调用.()方法,与调用.()方法类似。 过程。 它创建了一个栈帧,将三个参数的值存储在局部变量表中等等,这里就不画图了...

从.()返回后,栈顶用于传递参数的三个值被回收。

:将slot0处的参考值压入堆栈。

:将slot3处的参考值压入堆栈。

#2:将当前栈顶的值“”赋给引用对象的sid字段,然后将这两个值弹出栈中。

:返回调用者,main()方法,并弹出当前方法栈帧。

返回main()方法,继续执行以下字节码指令:

:将栈顶当前引用类型的值赋给slot1处的局部变量,然后弹出栈。

5、至此,第一行代码已经执行完毕,s返回到局部变量表,接下来执行下面的代码。

无效主([]参数){

s = new(23,“”,“”); // 执行完成

s.研究(5,6);

.();

s.run();

}11

:将slot1处的引用类型值压入栈中

:将常量5压入堆栈。 只有0-5个int类型常量有对应的指令。

6:将常量6压入堆栈

6.开始执行第二行代码,也就是方法

#11:调用虚方法study()。 该方法是重写接口中的方法,需要动态调度,所以使用指令。

为 Study() 方法创建一个堆栈帧:

最大堆栈深度3,局部变量表5

该方法的java源代码:

在这里编写代码片段 int Study(int a, int b) {

整数 c = 10;

整数d=20;

a+b*cd;

}

10:将10压入堆栈

:将栈顶的值10赋值给slot3处的int局部变量,即c,并出栈。

20:将20压入堆栈

4:将栈顶的20付给槽4处的int局部变量,即d,并出栈。

以上4条指令完成了c和d的赋值。

,,这三个指令将三个局部变量slot1、slot2和slot3压入堆栈:

imul:将栈顶的两个值弹出,并将相乘结果压入栈:

iadd:弹出当前栈顶的两个值,并将相加结果压入栈

iload 4:将slot4处的int型局部变量放入

isub:将栈顶的两个值弹出,并将相减结果压入栈:

:将栈顶的当前值返回给调用者。

7、至此,第二行代码已经执行完毕,将返回值返回给s,执行下面的内容。

无效主([]参数){

s = new(23,“”,“”); // 执行完成

s.研究(5,6);

.();

s.run();

}11

#12 调用静态方法()不需要传递任何参数

pop:()方法有返回值,将其从堆栈中弹出

:将slot1处的参考值压入堆栈

#13:调用对象的run()方法,该方法重写了父类的方法,需要动态调度,所以使用指令

:main() 返回,程序结束。