类的加载:
将类的.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