1.JDK与JRE的作用
当我们编写Java程序时,我们实际上是在使用一种高级编程语言,而计算机最终需要将这些高级语言转换成机器语言才能执行。在这个过程中,Java开发工具包(JDK)和Java运行时环境(JRE)扮演了重要角色:
- JDK:JDK是一个完整的JAVA开发工具包,它包含了编译器(JAVA Compiler即javac)、库和开发工具。JDK的编译器作用就是把.java文件变成.class文件的过程,即把高级语言转化成编译语言(字节码)。
- JRE:JRE是JAVA运行时环境,它包含了JAVA虚拟机(JVM)、JAVA核心类库和支持文件。其中重点就是JVM,JVM中包含了JVM解释器,它的作用是将Java程序编译后的字节码文件(.class文件)解释成机器语言,从而让计算机能够执行Java程序。
这一过程也就是为什么JAVA常被称为“半编译、半解释语言”(主编译)。
为什么说JAVA的生命周期要说到JDK和JRE,我是觉得了解Java程序从编写到运行再到结束的整个生命周期过程,不仅涉及到JDK和JRE的角色,还需要理解JAVA程序再不同阶段的处理和运行方式。
这种全面的了解可以帮助我们更好地理解Java地设计概念和底层结构,以及Java程序再运行时的行为和性能表现。
在上面的内容我们也可以得知,在字节码加载到内存前,其实还有一个编译过程就是 .java->.class,从这一步后,就是整个JAVA类生命周期的开始。
2. JAVA类的生命周期
- 加载(Loading):ClassLoader将字节码文件加载到内存中,并创建一个代表该类的Class文件。
- 链接(Linking)
- 验证(Verification):确保加载的类符合JVM规范,不会导致安全问题
- 准备(Preparation):为类的静态变量分配内存,并设置默认初始值。
- 解析(Resolution):将类、方法字段等符号引用解析为直接引用
- 初始化(Initialization):执行类的初始化代码,包括静态变量赋值和静态初始化块的执行。
- 运行(Running):实例化类并调用其方法,执行程序逻辑
- 卸载(Unloading):当类不再需要时,ClassLoader可以卸载类,并释放相关资源。
3.生命周期(类加载)—加载(Loading)
ClassLoader负责加载字节码文件(.class文件)到内存中。ClassLoader有三种:Bootstrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)和Application ClassLoader(应用程序类加载器)。它们会按照一定的顺序来尝试加载类文件,最终加载的类文件会被转换成二进制形式并存储在内存中。
一旦类文件被加载到内存中,JVM会为这些类创建对应的‘class’对象。这个对象包含了类的结构信息,比如类的方法、字段、构造函数等。‘Class’对象会被创建并存储在方法区,这个对象在整个类的生命周期都存在,直到程序结束才会被销毁。
在JAVA虚拟机(JVM)中,‘Class’对象通常都是存在方法区(Method Area)中的。方法区是一块用于存储类信息、常量、静态变量等数据的内存区域,它是线程共享的内存区域,在JVM启动时被创建。方法区属于堆内存的一部分,但是它有一些特殊行为,例如不会进行垃圾回收。
类的元数据信息和类的对象实例的存储空间是不同的,类元数据信息在方法区,实例对象是在堆内存中。
4.生命周期(类加载)—链接(Linking)
连接分为三个阶段:验证->准备->解析
- 验证:确保加载的类符合JVM规范,不会导致安全问题。这个不过多解释,正常开发应该不会坑在这里。
- 准备:为类的静态变量分配内存并设置初始值
- 为类静态变量分配内存空间。这时候静态变量会被分配到方法区中的静态变量区域。
- 设置静态变量的默认初始值。根据静态变量的类型赋予默认值。 例如,int 类型的默认值为 0,boolean 类型的默认值为 false,引用类型的默认值为 null。
注意:准备阶段并不会真正为静态变量赋予程序中的指定值,而只是为它们分配内存并设置默认初始值。
- 解析:在这个阶段,虚拟机将常量池中的符号引用替代为直接引用。
- 将类、方法、字段的符号引用转换为直接引用:在编译阶段,Java编译器生成的class文件会包含许多符号引用,比如类名、方法名、字段名等。在解析阶段,虚拟机会将这些符号引用替代为直接引用,指向内存中的实际数据结构,比如指向方法区中的方法表、字段表等。
符号引用:以一组符号描述所引用的目标。在编译时,因为类还没加载到方法区,java类并不知道引用的对象的实际地址,因此只能使用符号引用替代。比如com.Test类类引用了com.MyMO类,编译时Test类并不知道MyMo类的实际内存地址,因此只能使用符号com.MyMo
直接引用:通过对符号引用进行解析,找到引用的实际地址。
- 在Java虚拟机中,解析过程可以延迟到进行时进行,这种延迟解析方式可以提高程序性能。
例子:延迟解析的一个例子是动态链接。在动态链接的情况下,方法的符号引用会被解析为实际的内存地址,但是这个解析过程可以延迟到方法被调用时进行,而不是在类加载阶段就完成。这样可以避免在类加载时就解析所有的方法引用,提高了程序的性能。
5. 生命周期(类加载)—初始化(Initialization)
在Java中,类的初始化阶段是类加载过程的最后一个阶段,在这个阶段,JVM会执行类的初始化代码,包括静态变量赋值和静态初始化块的执行。
- 静态变量赋值:在准备阶段,静态变量被分配了内存并设置了默认值,而在初始化阶段,会执行静态变量的赋值操作,将静态变量设置为程序中指定的值。
- 静态初始化块的执行:类中的静态初始化块会在初始化阶段被执行。静态初始化块可以包含任意的java代码,用于完成一些静态资源的初始化工作。
- 可执行静态方法: 只有在静态方法被显式调用或者静态方法在静态初始化块中被调用时,才会执行这些静态方法
至此,类的加载完毕,一共经历四个阶段,规范检查(检查)->类元数据加载并赋值默认值(准备)->符号引用变为直接引用(解析)->静态变量赋值、静态代码块执行(初始化)
6.运行(Running)与卸载(Unloading)
- 运行:在这个阶段,逻辑执行,类的实例被创建,并且可以通过调用实例方法执行程序功能
- 卸载:类的卸载也是由ClassLoad进行管理的,通常在类加载器被垃圾回收时才会触发。正常不会触发
在正常情况下,Java程序运行过程中很少会发生类的卸载。这是因为在Java虚拟机中,类的卸载通常需要满足下面条件:- 类实例的引用数量为零:即没有任何类的实例被引用,包括静态变量对该类的引用。
- 类的ClassLoader被卸载:ClassLoader负责加载类到内存中,当ClassLoader被卸载时,它加载的类也会被卸载。
- 没有被其他类引用:即没有其他类通过反射等方式引用该类。
在正常情况下,这些条件很难同时满足,所以类的卸载在正常程序运行时并不常见。但是,在某些特殊情况下,比如使用自定义的ClassLoader动态加载类时,可能会出现类的卸载。