类的加载
java在需要使用类的时候才会将类加载,如果只是定义了一个类的变量则不会加载类
java的类加载是由类加载器来完成的
JVM在加载类的时候会经历以下步骤
- 加载:找到相应的class字节码文件,由类加载器将类的信息读入到JVM中。如果找不到该类则会抛出ClassNotFoundException异常
- 验证:确认读取文件类型的正确性,即class文件是否符合java规范并且不会损害JVM的完整性
- 准备:为静态成员变量分配内存空间同事设置默认初始值
- 解析:把符号引用转为直接引用的过程。例如将变量名转为内存地址。以后使用这个成员变量时,就可以直接去这个内存地址找了。同时类的成员方法也会被映射到某个内存地址中等待调用
类的初始化
类的初始化阶段会为静态成员变量赋予正确的初始值,并且调用类中所有的静态代码块
- 调用静态成员变量的初始化代码
- 例如类中定义了一个静态成员变量
static int i = 1;
之前准备阶段已经为变量i
分配了内存空间,并且将i
的值设为了初始值(即0),此时才会将i
的值设为1
- 例如类中定义了一个静态成员变量
- 调用类中所有的静态代码块
事实上,编译阶段java编译器会将类中所有的静态变量初始化代码和静态代码块封装到一个clinit方法中,此时JVM会调用这个方法
- clinit方法程序员不能调用,只能被JVM调用
- clinit方法只在初始化阶段执行且只被执行一次,因此静态变量只被初始化一次,静态方法也只被调用一次
类的实例化
在类初始化完成以后,这个类就可以随时使用了。但是此时只能使用该类的静态成员(变量和方法)
当创建一个类的对象时(使用new),此时进行的是对象实例化操作
- 在堆中为实例变量分配空间
- 调用类中所有的实例化代码块
- 调用相应的构造方法
事实上,编译阶段java编译器会将类中所有的实例变量和初始化代码和实例代码块封装到一个init方法中,此时JVM会调用这个方法
- init方法程序员不能调用,只能被JVM调用
- init方法在每次被实例化时都会被调用
- 实例代码块按源文件顺序执行,最后才会调用构造方法
类初始化和实例化的区别
- 初始化完成之后才会进行实例化的操作
- 初始化是为静态成员变量赋值,是在方法区(持久代)中分配空间。实例化是为实例变量赋初值,是在堆内存中分配空间
- 初始化完成之后只能使用该类的静态成员(变量和方法),实例化完成之后即可使用该类的所有成员
类的初始化和实例化方法
- 使用new创建对象
new <类名>(); //加载、初始化、实例化
- 使用Class类forName方法
Class c = Class.forName(<类名>); //加载、初始化 c.newInstance(); //实例化
- 使用ClassLoader类loadClass方法
Class c = ClassLoader.getSystemClassLoader().loadClass(<类名>); //加载 c.Instance(); //初始化、实例化
类的初始化和实例化的顺序
没有父子类的情况
- static变量和static初始化代码块
- 实例变量和实例初始化代码块
- 构造方法
有父子类的情况
- 父类的static变量和static初始化代码块
- 子类的static变量和static初始化代码块
- 父类的实例变量和实例化初始化代码块
- 父类的构造方法
- 子类的实例变量和实例初始化代码块
- 子类的构造方法