类加载过程,以及类加载器
- 一、类加载的过程
- 二、类加载器介绍
- 三、跨类加载
- 三、举例说明
一、类加载的过程
类加载是Java虚拟机(JVM)将类文件加载到内存中并转换成对应的类对象的过程。它确保了类文件能够正确加载并转换成可执行的类对象,为程序的执行提供了必要的基础设施。类加载过程主要包括以下几个步骤:
- 加载(Loading):加载是指将类的字节码文件(.class文件)从磁盘或者网络中读取到内存中的过程。类加载器根据类的全限定名(Fully Qualified Name)来定位和加载类文件。加载过程不包括对类文件的解析和验证,仅仅是将字节码加载到内存中形成类对象的初始状态。
- 验证(Verification):验证是指对加载的类文件进行字节码验证、符号引用验证等过程,确保类文件的格式和内容符合Java虚拟机规范,防止恶意代码或者不规范的代码对系统造成安全漏洞或者错误。
- 准备(Preparation):准备是指为类的静态变量分配内存空间,并设置默认初始值的过程。在准备阶段,静态变量会被初始化为默认值,例如整数类型的默认值是0,引用类型的默认值是null。
- 解析(Resolution):解析是指将类中的符号引用转换为直接引用的过程。在Java中,类的方法调用、字段访问等操作都是通过符号引用来实现的,解析阶段将这些符号引用转换为实际内存地址,使得程序可以直接访问类的方法和字段。
- 初始化(Initialization):初始化是指执行类的初始化代码(静态代码块和静态变量赋值语句)的过程。在初始化阶段,JVM会按照类加载的先后顺序依次执行静态代码块和静态变量赋值语句,完成类的初始化工作。
需要注意的是,类加载过程是按需进行的,并不是一次性加载所有类文件。当程序需要使用某个类时,类加载器会先加载该类及其所依赖的类,然后进行验证、准备、解析和初始化操作。类加载器会按照委派模型逐级向上委托加载类,直到找到合适的类加载器为止。
对于已经加载的类会被放在 ClassLoader
中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。
下面这个截图是ClassLoader类,里面有一个Vector变量用来存由这个类加载器加载的类。
二、类加载器介绍
具体来说,Java虚拟机中的类加载器主要分为以下几种:
- 启动类加载器(Bootstrap Class Loader):负责加载Java的核心类库,例如java.lang.Object等。
- 扩展类加载器(Extension Class Loader):负责加载Java的扩展类库,例如java.lang.String等。
- 应用程序类加载器(Application Class Loader):也称为系统类加载器,负责加载应用程序的类,例如项目中自定义的类。
- 自定义类加载器:开发者可以根据需要编写自定义的类加载器,用于加载特定的类或者资源。
三、跨类加载
跨类加载器指的是在Java虚拟机(JVM)中存在多个类加载器,这些类加载器可能会加载同一个类,导致在内存中存在多个版本的类实例。这种情况下,如果涉及到静态变量,可能会出现静态变量在不同类加载器加载的类中存在多份实例的情况。
当存在多个类加载器时,如果不同的类加载器加载了同一个类,就会出现类实例的多个版本。对于静态变量,它们属于类级别的变量,在类加载时被初始化,因此不同类加载器加载的同一个类中的静态变量会有不同的实例。
这种情况下,如果静态变量被跨类加载器共享,可能会导致数据不一致的问题。因此,在设计应用程序时,需要考虑到类加载器的影响,合理使用静态变量,避免跨类加载器共享静态变量可能带来的问题。
三、举例说明
假设有一个简单的Java应用程序,其中包含一个类 MyClass
,以及一个静态变量myStaticVariable
,同时存在两个不同的类加载器A和B。
public class MyClass {public static int myStaticVariable = 0;public static void main(String[] args) {MyClass.myStaticVariable = 10;System.out.println("静态变量值:" + MyClass.myStaticVariable);}
}
现在,我们假设类加载器A
加载了MyClass
类,而类加载器B也加载了同一个 MyClass
类。由于静态变量属于类级别的变量,因此每个类加载器都会为静态变量myStaticVariable
分别创建一个实例。
假设在类加载器A
中的MyClass
类中修改了静态变量的值:
public class MyClass {public static int myStaticVariable = 0;static {myStaticVariable = 20;}public static void main(String[] args) {System.out.println("静态变量值:" + MyClass.myStaticVariable);}
}
而在类加载器B
中的MyClass
类中没有修改静态变量的值:
public class MyClass {public static int myStaticVariable = 0;public static void main(String[] args) {System.out.println("静态变量值:" + MyClass.myStaticVariable);}
}
那么,当在不同的类加载器中运行这两个版本的MyClass类时,会得到不同的输出结果:
在类加载器A中运行时,输出结果为"静态变量值:20",因为在类加载时,静态代码块将静态变量myStaticVariable的值设置为20。
在类加载器B中运行时,输出结果为"静态变量值:0",因为没有修改静态变量的值,保持默认值0。
这种情况下,就展示了静态变量在跨类加载器情况下可能出现的多份实例的情况。