Java的运行机制

1.JAVA类的执行过程

使用Java语言进行程序设计时,不仅要了解Java语言的特点,还要了解Java程序的运行机制。Java程序运行时,必须经过编译和运行两个步骤。首先将扩展名为java的源文件进行编译,生成扩展名为class 的字节码文件,然后Java虚拟机将字节码文件进行解释执行,并将结果显示出来。


为了让初学者能更好地理解Java程序的运行过程,接下来以1.4节中的例1-1为例,通过图1-17来详细地分析程序的执行过程。

Clipboard Image.png


图1-17中的具体执行步骤分析如下:

  • 编写一个Java源文件Hello World.java。
  • 使用javac HelloWorld.java”命令开启Java编译器并进行编译。
  • 编译结束后,会自动生成一个HelloWorld.class 的字节码文件。
  • 使用"java HelloWorld”命令启动Java虚拟机运行程序,Java 虚拟机首先将编译好的字节码文件加载到内存,这个过程被称为类加载,它是由类加载器完成的,然后虚拟机通过Java解析器对加载到内存中的Java类进行解释执行。
  • 执行后会生成计算机可以识别的机器码文件。
  • 机器码文件在计算机运行并显示结果。


通过上面的分析不难发现,Java程序是由Java 虚拟机负责解释执行的,而并非操作系统。这样做的好处是可以实现Java程序的跨平台运行。也就是说,在不同的操作系统上,可以运行相同的Java程序﹐只需安装不同版本的Java 虚拟机即可。

图 1-18所示为Windows , Linux . MacOS操作系统上的Java虚拟机。

image.png

从图1-5可以看出,JDK中包含了开发工具和JRE,而JRE中又包含了Java基础类库和JVM。其中JVM是运行Java程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要类加载器,字节码校验器以及Java的基础类库等。如果只是运行Java程序﹐可以只安装JRE,而无须安装JDK。如果要开发Java程序,则必须安装JDK。

2.字节码(.class)文件的加载过程

2.1 类加载

 在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的,类型可以是Class,Interface, 枚举等。

2.2 Java虚拟机与程序的生命周期

在如下几种情况下,Java虚拟机将结束生命周期

  • 1)执行了System.exit() 方法
  • 2)程序正常执行结束
  • 3)程序在执行过程中遇到了异常或者错误而异常终止。
  • 4) 由于操作系统出现错误导致Java虚拟机进程终止。

2.3 字节码文件的装载过程

 加载、连接(包括三个步骤: 验证 准备 解析)、初始化

加载查找并加载类的二进制数据

连接

验证确保被加载的类的正确性

准备:为类的静态变量分配内存,并将其初始化为默认值

解析:把类中的符号引用转换为直接引用

初始化: 为类的静态变量赋予正确的初始值

image.png


2.4 Java 符号引用 与 直接引用

符号引用:通俗的讲,是一种间接引用,如一个类中的方法引用了另外一个类,这是一种符号的表述。

直接引用:就是通过指针的方式,直接指向了目标对象内存的位置,这样能一下子找到特定的方法。

举例:

在类的加载过程中的解析阶段,Java虚拟机会把类的二进制数据中的符号引用 替换为 直接引用,如Worker类中一个方法:

publicvoid gotoWork(){

     car.run(); //这段代码在Worker类中的二进制表示为符号引用        

}

在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名 和 相关描述符组成。在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。

2.5 类装载的条件

Java程序对类的使用方式分为两种:主动使用 、被动使用

所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。

Java虚拟机规定: 一个类或者接口在初次使用时,必须进行初始化。

这里的使用指主动使用,主动使用有以下几种情况:

  • 当创建一个类的实例时。 比如使用new关键字,或者通过反射、克隆、反序列化方式。
  • 当调用类的静态方法时。即当使用了字节码invokestatic指令
  • 当使用类或者接口的静态字段时(final常量除外,此种情况只会加载类而不会进行初始化),即使用getstatic或者putstatic指令(可以使用jclasslib软件查看生成的字节码文件),或者对该静态字段赋值(putstatic指令)。
  • 当使用java.lang.reflect包中的方法反射类的方法时。如Class.forName("com.example.Test")
  • 当初始化子类时,必须先初始化父类
  • 作为启动虚拟机,含有main方法的那个类
  • JDK1.7开始提供的动态语言支持
  • java.lang.invoke.MethodHandle实例的解析结果REF_getStatic, REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。

除了以上情况属于主动使用外,其他情况均属于被动使用,被动使用不会引起类的初始化,只是加载了类却没有初始化。(这里的初始化就是类加载的第三个阶段)

3.类装载的过程

3.1 加载类: 处于类装载的第一个阶段

第一步:类的加载指的是将类的.class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范中并未说明Class对象位于哪里,HotSpot虚拟机将其放在方法区中),用来分装类在方法区内的数据结构。


加载类时,JVM必须完成

1)通过类的全名,获取类的二进制数据流。

2)解析类的二进制数据流为方法区内的数据结构,也就是将类文件放入方法区中

3)创建java.lang.Class类的实例,表示该类型


第二步:加载.class 文件的方式

1)从本地系统中直接加载

2)通过网络下载.class文件

3)从zip,jar等文档文件中加载.class 文件

4)从专有数据库中提取.class文件

5)将Java源文件动态编译为.class 文件(动态代理,JSP(JSP文件本质上是Sevlet,最终被编译为.class文件))

3.2 连接

1)  验证字节码

验证字节码文件: 当类被加载到系统后,就开始连接操作,验证就是连接的第一步

主要目的是保证加载的字节码是否符合规范

验证的步骤如图:

image.png


2) 准备阶段

当一个类通过验证后,虚拟机就会进入准备阶段。准备阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值,这些内存都将在方法区进行分配。这个时候进行内存分配的仅是类变量,不包括类实例变量,实例变量将会在对象实例化时随着对象一起分配在堆上。为类变量设置初始值是设为其数据类型的“零值”。

比如: public static int num = 10; 这个时候就会为num变量赋值为0

Java虚拟机为各种类型变量默认的初始值如表:

image.png

注意:java并不支持boolean类型,对于boolean类型,内部实现试Int, 由于int的默认值是0, 故对应的boolean的默认值是false。

如果类中属于常量的字段,那么常量字段也会在准备阶段被附上正确的值,这个赋值属于java虚拟机的行为,属于变量的初始化。在准备阶段,不会有任何java代码被执行。


3)  解析类

解析阶段的任务就是将类、接口、字段和方法的符号引用直接转为直接引用。

符号引用就是一些字面量的引用。比较容易理解的就是在Class类文件中,通过常量池进行大量的符号引用。

3.3 初始化

为类的静态变量赋予正确的初始值。

除了上面三个步骤,加载,连接,初始化,还有下面两个过程:使用和卸载

3.4 使用

 如类创建一个对象,调用类里的方法。

3.5 卸载

class 文件加载到内存中,形成了一个自己的数据结构驻留在内存中,还可以从内存中销毁掉,称之为卸载。卸载以后,就不能再创建类的对象了。可以重新加载到内存中。(开发人员很少使用卸载)

以上就是类加载的5个过程。

4. JDK、JVM、JRE的区别

image.png


参考文章:https://blog.csdn.net/m0_52226803/article/details/129656682

腾讯云推出云产品限时特惠抢购活动:2C2G云服务器7.9元/月起
本文链接:https://www.jhelp.net/p/jbGF4ojshY9UzvVK (转载请保留)。
关注下面的标签,发现更多相似文章