jvm(二)类的加载,连接,初始化详解

时间:2021-07-13 17:28:27

类的加载过程由类加载器来完成:


注意:(类加载器加载类到内存不会执行该类的初始化,只有类第一次主动使用才会初始化!!!)


如:ClassLoader.getSystemClassLoader().loadClass("cn.xt.A"); 这句话调用系统类加载器加载A类,虽然A类被加载到内存中,

但是A类不会执行初始化。


4.类加载器
java有2种类型的类加载器:
4.1 java自带的类加载器
4.1.1根类加载器(BootStrap,使用c++编写,无法在java代码中获得)
[根类加载器加载的Class对象调用getClassLoader方法将返回null,如Class.forName("java.lang.String").getClassLoader()]
4.1.2扩展加载器(Extension,使用java编写)
4.1.3系统加载器(System,又叫做应用加载器,使用java编写)
4.2用户自定义的类加载器(继承java.lang.ClassLoader抽象类)

类的加载与类的初始化不同,它并不需要类被首次主动使用,而是在类加载器预料到类将要被使用时就预先加载类。
如果预先加载过程中发现class文件缺失,或者存在错误(LinkageError),类加载器必须等到类被首次主动使用才报告错误。
如果类一直没有被主动使用,那么这个错误将一直不会报告。


类加载完成就进入连接阶段

1.类的验证

jvm(二)类的加载,连接,初始化详解

2.类的准备:为静态变量分配内存空间并赋默认值

3.类的解析

jvm(二)类的加载,连接,初始化详解

符号引用:就是java代码中类被转成二进制数据后包含的引用的描述,一般由指向的对象全名+相关描述符组成。

直接引用:将符号引用替换成指针,这个指针才真正指向内存地址。


类连接完成即进入初始化阶段

jvm(二)类的加载,连接,初始化详解


类的初始化的顺序:

1.如果类还没有加载或者连接,先进行加载和连接

2.如果类中存在初始化语句(static代码块或者直接赋值static变量),则从上往下依次执行初始化语句

3.如果类存在直接的父类且父类还没有初始化,则先初始化直接的父类。


接口的初始化:

类只有在第一次主动使用时才被初始化,而主动使用6条中有1条规定:当子类初始化前会先执行直接父类的初始化,

但是这个条件对于接口不适用,即:

当类存在直接接口,或者接口存在直接接口时,子类或者子接口初始化时并不会先初始化它们的父接口。


直接定义在子类中的静态变量或者静态方法被第一次使用才会算作子类的第一次主动使用,如果使用的是父类继承过来的变量或者方法,不能

算作子类的第一次主动使用,不会初始化子类,如:

class A {
public static int a = 0;
}
class B extends A{
static{
System.out.println("bbb");
}
}

使用B.a不会执行B类的初始化(即静态代码块不会执行)。


另:类或接口访问final修饰的static变量有如下区别:

如果变量是编译时常量(编译时值就确定了),则不会导致类被初始化。否则,则会导致类被初始化。(static代码块会被执行)

public class FinalStaticTest {
public static void main(String[] args) {
System.out.println(Test1.a);//Test1的初始化操作不会执行,因为Test1.a是编译时常量
System.out.println(Test2.a);//Test2的初始化操作会执行,因为Test2.a非编译时常量
}
}

class Test1 {
public static final int a = 100/10;//编译时a的值就确定了(10),称为编译时常量。
static {
System.out.println("test1 static block...");
}
}

class Test2 {
public static final int a = new Object().hashCode();//编译时a的值不确定,非编译时常量
static {
System.out.println("test2 static block...");
}
}