类的加载:
将类的.class文件中的二进制数据读入到内存,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用于封装类在方法区内的数据结构;JVM将类的加载过程分为三个步骤:装载、链接、初始化
1.装载:
查找并加载类的二进制数据到内存,加载class文件方式有以下几种:
从本地文件系统加载,通常是我们自己编写的类
通过网络下载.class文件
从zip、jar等归档文件中加载,通常是引用的第三方的jar文件
从专有数据库中提取.class文件
将java源文件动态编译为.class文件
JVM通过类的全限定名及类加载器完成类的加载。JVM在装载类时按照按需动态装载的原则装载。
2.连接:
-验证:确保被加载的类的正确性
-准备:为类的静态变量分配内存,并将其初始化为默认值
-解析:将类中的符号引用转换为直接引用
3.初始化:
为类的静态变量赋予正确的初始值,包括执行static代码块及静态属性的初始化;只有当类在被主动使用时,才会触发初始化过程;类的主动使用包括:
-创建类的实例
-访问某个类或接口的静态变量,或者对该静态变量赋值(只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时才可以认为是对类或接口的主动使用)
-调用类的静态方法
-反射,如Class.forName()
-初始化一个类的子类
-java虚拟机启动时被表明为启动类的类,如包含main方法的类
类的初始化按照以下原则进行:
- 类从顶至底的顺序初始化,所以声明在顶部的字段的早于底部的字段初始化,包括static代码块
- 超类早于子类和衍生类的初始化
- 静态域的初始化是在类的静态初始化期间,非静态域的初始化时在类的实例创建期间。这意味这静态域初始化在非静态域之前。
- 非静态域通过构造器初始化,子类在做任何初始化之前构造器会隐含地调用父类的构造器,他保证了非静态或实例变量(父类)初始化早于子类
示例:创建一个对象Person
public class Person {
private static int hands;
static {
System.out.println("Person static set hands = 1");
hands = 1;
}
public Person() {
System.out.println("Person construct set hands = 2");
hands = 2;
}
public static int getHands() {
return hands;
}
}
测试1:
@Test
public void testLoad1() {
System.out.println(Person.class.getName());
}
控制台输出:
com.jiangnan.classloader.load.Person
可以看出,Class Person已经被加载到JVM了,但是静态代码块及构造器均未执行,因为Person.class.getName()调用不符合类的主动使用;
测试2:
@Test
public void testLoad2() {
System.out.println(Person.getHands());
}
控制台输出:
Person static set hands = 1
1
可以看出,对静态方法的调用,会导致类的初始化,执行static代码块,但并不会调用构造函数;
测试3:
@Test
public void testLoad3() {
Person person1 = new Person();
Person person2 = new Person();
}
控制台输出:
Person static set hands = 1
Person construct set hands = 2
Person construct set hands = 2
当通过 new 关键字创建 类的实例对象时才会调用构造函数,静态代码块是在类加载后初始化阶段执行的,优先于构造函数的执行,并且只会执行一次;
接下来创建另一个对象User,继承Person
public class User extends Person {
static {
System.out.println("User static set age1 = 2");
age1 = 2;
}
public static int age1 = 0;
public static int age2;
}
测试4:
@Test
public void testLoad4() {
System.out.println(User.age1);
System.out.println(User.age2);
}
控制台输出:
Person static set hands = 1
User static set age1 = 2
0
0
把User类中static代码块的位置换一下:
public class User extends Person {
public static int age1 = 0;
static {
System.out.println("User static set age1 = 2");
age1 = 2;
}
public static int age2;
}
执行testLoad4,控制台输出如下:
Person static set hands = 1
User static set age1 = 2
2
0
可以看出,对静态属性的访问会导致类的初始化;不管怎样,父类的初始化总是优先于子类; static块跟静态field优先级相同,谁定义在前面,谁就先执行。
测试5:接下来为User类加上一个私有的构造器,并且定义一个私有的User属性,修改如下:
public class User extends Person {
private static User user = new User();
public static int age1 = 0;
static {
System.out.println("User static set age1 = 2");
age1 = 2;
}
public static int age2;
private User() {
System.out.println("User construct execute");
age1++;
age2++;
}
}
控制台输出如下:
Person static set hands = 1
Person construct set hands = 2
User construct execute
User static set age1 = 2
2
1
首先,父类优先加载、初始化,所以先输出父类的信息,其次初始化静态的user对象,导致私有的构造器执行,此时age1、age2均为1,接下来执行age1的赋值,置为1,执行static块将age1置为2,age2已经有初始值了,不会再初始化,因此输出age1=2,age2=1;
接下来将 private static User user = new User();的定义换个位置,如下所示:
public class User extends Person {
public static int age1 = 0;
static {
System.out.println("User static set age1 = 2");
age1 = 2;
}
public static int age2;
private static User user = new User();
public User() {
System.out.println("User construct execute");
age1++;
age2++;
}
}
继续执行testLoad4(),控制台输出如下:
Person static set hands = 1
User static set age1 = 2
Person construct set hands = 2
User construct execute
3
1
private static User user = new User();的执行导致构造函数的执行,将age1和age2累加1,因此输出3、1
测试6:User类代码不变,如下:
public class User extends Person {
public static int age1 = 0;
static {
System.out.println("User static set age1 = 2");
age1 = 2;
}
public static int age2;
private static User user = new User();
public User() {
System.out.println("User construct execute");
age1++;
age2++;
}
}
测试代码修改如下:
@Test
public void testLoad5() {
User user = new User();
System.out.println(User.age1);
System.out.println(User.age2);
}
控制台输出如下:
Person static set hands = 1
User static set age1 = 2
Person construct set hands = 2
User construct execute
Person construct set hands = 2
User construct execute
4
2
User user = new User();代码主动使用了User类,导致User类被加载并初始化,由于有父类,先初始化Person,因此先输出Person static set hands = 1,接着初始化User自身,执行static块输出User static set age1 = 2,初始化 age2 赋值为默认值0,创建静态 user 属性,由于User已经被加载,不会重复加载、初始化,执行User的构造函数,执行前先执行父类的构造函数,输出Person construct set hands = 2,执行本类的构造函数输出User construct execute,这两次构造函数调用是创建User类中的私有静态User属性 private static User user = new User()时调用的,最后执行测试方法testLoad5()中User user = new User()时产生两次构造函数调用。
测试7:
@Test
public void testLoad6() {
User.getHands();
}
运行如上测试类,控制台输出如下:
Person static set hands = 1
User类的静态代码块没有被执行,即User类没有初始化
测试代码位于 https://github.com/ywu2014/ClassLoader
相关推荐
### 类加载器详解 #### 一、类加载器概述 **类加载器(ClassLoader)**是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译好的`.class`文件加载到JVM中,使得这些类可以在Java环境中运行。类加载器不仅能够加载类...
#### 二、类加载过程 类加载过程主要包括三个步骤: 1. **加载**:通过类的全限定名找到该类的二进制字节流。 2. **连接**: - **验证**:确保加载的类文件符合JVM规范,包括字节流文件的格式正确性、元数据的...
在加载阶段,类加载器寻找并加载类的二进制数据。 - Java中的类加载器采用双亲委派模型,即一个类首先由启动类加载器Bootstrap ClassLoader尝试加载,如果找不到则交给扩展类加载器Extension ClassLoader,再找不到...
#### 二、类加载器系统 Java中的类加载器系统主要包括以下几种类型的类加载器: 1. **Bootstrap ClassLoader**(启动类加载器):它是整个类加载器系统的根,负责加载存放在`<JDK_HOME>\lib`目录中的核心类库。该...
#### 二、类加载器层次结构 当JVM启动时,会形成一个由三个主要类加载器组成的层次结构:bootstrap class loader(引导类加载器)、extension class loader(扩展类加载器)以及system class loader(系统类加载器...
Java 类加载机制是Java技术体系中的重要组成部分,它关乎到程序运行时的类查找与实例化。当遇到`java.lang.ClassNotFoundException`异常时,通常是因为类加载过程出现了问题。了解类加载机制对于解决这类问题至关...
- 类加载器查找并加载类的二进制流。 - 创建`java.lang.Class`对象,代表这个类。 2. **连接**(Linking): - **验证**(Verification):确保输入的类文件符合JVM规范,不会危害JVM安全。 - **准备**...
#### 二、类加载过程 ##### 2.1 加载 加载阶段的主要任务是通过类的全限定名获取定义此类的二进制字节流,并将其转换成方法区的运行时数据结构。此外,还会在堆中生成一个 `java.lang.Class` 对象,作为该类数据的...
系统类加载器在加载类时,会先尝试让扩展类加载器加载,如果扩展类加载器无法加载,则再由系统类加载器自己尝试加载。这样的设计是为了保证核心类库的唯一性和安全性,避免用户自定义的类覆盖了 JDK 内置的核心类。 ...
其中,`Class.forName()`方法有两种重载形式,第一个只接受类名作为参数,第二个允许控制是否初始化类以及指定类加载器。如果只使用一个参数的`Class.forName()`方法,那么它会默认使用调用者的类加载器并初始化类。...
Java类加载器是Java虚拟机(JVM)的关键组成部分,它负责查找并加载类到内存中,使得程序能够运行。自定义Java类加载器允许我们根据特定需求扩展默认的加载机制,例如,从非标准位置加载类或者实现动态加载。在Java...
#### 二、类加载器的重要性 类加载器是Java运行时环境中的一个重要组成部分,它负责在程序运行时按需加载类。不同的类加载器模式可以影响到类的加载顺序,进而影响到应用程序的行为。例如,在某些场景下,如果旧...
加载阶段是类加载器的主要工作,它负责找到类的二进制表示并将其转化为Class对象。加载器按照双亲委派模型工作,即从顶级的启动类加载器开始,逐级向下查找,直到找到目标类。 2. 类加载器类型: - 启动类加载器...
1. **加载**:在这一阶段,类加载器会根据类名找到对应的二进制数据流,这个数据流可以来自多种来源,如文件系统、网络或者自定义的类加载器。加载完成后,JVM会创建一个表示该类的Class对象。 2. **验证**:验证是...
2. **类加载器及类加载器的委托机制**:JVM中有三种内置的类加载器,分别是启动类加载器、扩展类加载器和应用类加载器。此外,还可以自定义类加载器。类加载器之间遵循委托机制,即下级类加载器先请求上级类加载器...
### Java ClassLoader (类加载器)详解 #### 一、教程提示 如果你正在查看这份文档,在线版中你可以点击下面的任何主题直接跳转到相应的部分。 1. **教程提示** 2. **介绍** 3. **类加载器结构** 4. **编译类加载...
类加载过程包括通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构,在内存中生成一个代表整个类的java.lang.Class对象,作为方法区这个类的各种...
本文将深入探讨ClassLoader的工作原理和类加载机制,帮助开发者理解这个至关重要的概念。 1. 类加载机制概述 Java的类加载机制遵循“双亲委派模型”(Delegation Model)。当一个类被加载时,它首先会尝试由当前...
#### 二、类加载机制概述 在Java中,类加载器(ClassLoader)负责将.class文件加载到JVM中。Tomcat通过自定义的类加载器实现了特定的类加载顺序,以确保能够正确处理不同来源的类文件,避免类的重复加载和类版本冲突...