跳转至

1 类加载的时机

本章主要内容: 虚拟机如何加载这些Class文件,Class文件中的信息进入到虚拟机中后会发生什么变化。

类加载的时机

类加载机制:Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称为虚拟机的类加载机制。

主动引用:有且仅有 6 种,必须立即对类进行“初始化”(而加载、验证,准备自然需要在此之前开始)

除此之外,所有引用类型的方式都不会触发初始化,称为被动引用

例子1:

package ClassLoading;

class SuperClass{
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

class SubClass extends SuperClass{
    static {
        System.out.println("SubClass init!");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}


//输出:
//SuperClass init!
//123

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

例子2:

package ClassLoading;

class SuperClass{
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

class SubClass extends SuperClass{
    static {
        System.out.println("SubClass init!");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        //使用数组定义来引用类,不会触发此类的初始化
        //程序不会输出任何数据
        SuperClass[] sca = new SuperClass[10];
    }
}

Java语言中对数组的访问要比C/C++相对安全,很大程度上就是因为这个类包装了数组元素的访问,而C/C++中则是直接翻译为对数组指针的移动。在Java语言里,当检查到发生数组越界时会抛出java.lang.ArrayIndexOutOfBoundsException异常,避免了直接造成非法内存访问。

例子3:

package ClassLoading;
class ConstClass{
    static {
        System.out.println("ConstClass init!");
    }
    public static final String HeLLOWORLD = "hello world";
}
public class NotInitialization {
    public static void main(String[] args) {
        //常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发 定义常量的类的初始化
        System.out.println(ConstClass.HeLLOWORLD);
    }
}
//输出: hello world

虽然在Java源码中确实引用了ConstClass类的常量HELLOWORLD,但其实在编译阶段通过常量传播优化,已经将此常量的值"helloworld"直接存储在NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用,实际都转化为NotInitialization类对自身常量池的引用了。也就是说,实际上NotInitialization的class文件之中并没有ConstClass类的符号引用入口,这两个类在编译成Class文件后就已不存在任何联系了。