1、类加载过程
其中连接又分为三个阶段:验证、准备、解析。
加载:就是把二进制形式的java类型读入到java虚拟机中
验证:
准备:为类的静态变量分配内存,设置默认值。但是在初始化之前,类变量都没有初始化为真正的初始值。
解析:解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用
初始化:为类变量赋予正确的初始值。
类实例化:为新的对象分配内存,为实例变量赋默认值,为实例变量赋正确的初始值
java编译器为他编译的 每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为<init>
。针对源代码中每一个类的构造方法,java编译器都产生一个<init>
方法。
2、类的加载
类的加载的最终产品是位于内存中的class对象
class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的入口。
3、类加载器
两种类型:
- java虚拟机自带的类加载器:
- 根类加载器(BootStrap)
- 扩展类加载器(Extension)
- 系统类加载器(System)
- 用户自定义的类加载器
- java.lang.ClassLoader的自雷
- 用户可以定制类的加载方式
4、类加载器加载类的时机
例:
public class MyTest1 {
public static void main(String[] args) {
System.out.println(MyChild1.str);
}
}
class MyParent1 {
public static String str = "hello world";
static {
System.out.println("MyParent1 static block");
}
}
class MyChild1 extends MyParent1 {
public static String str2 = "welcome";
static {
System.out.println("MyChild1 static block");
}
}
在这个例子里面,MyParent1被初始化,MyChild1没有被初始化。可以通过虚拟机参数-XX:+TraceClassLoading
来看MyChild1是否被加载:
[Loaded com.jvm.classloader.MyTest1 from file:/G:/JVM-lecture/target/classes/]
[Loaded sun.launcher.LauncherHelper$FXHelper from C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar]
[Loaded com.jvm.classloader.MyParent1 from file:/G:/JVM-lecture/target/classes/]
[Loaded com.jvm.classloader.MyChild1 from file:/G:/JVM-lecture/target/classes/]
MyParent1 static block
hello world
[Loaded java.lang.Shutdown from C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar]
可以看到,MyChild1确实被加载了。
结论:
类加载器并不需要等到某个类被“首次主动使用”时再加载他。 jvm规范允许类加载在预料到某个类将要被使用时就预先加载他,如果在预先加载的过程中遇到了class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误。 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
5、类的验证
类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。
类的验证的内容:
- 类文件的结构检查
- 语义检查
- 字节码验证
- 二进制兼容性的验证
6、类的初始化
类的初始化步骤:
- 假如这个类还没有被加载和连接,那就先进行加载和连接
- 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类
- 假如类中存在初始化语句,那就依次执行这些初始化语句
当java虚拟机初始化一个类时,要求他的所有父类都已经被初始化,但是这条规则并不适用于接口:
- 在初始化一个类时,并不会先初始化他所实现的接口
- 在初始化一个接口时,并不会先初始化他的父接口
因此,一个父接口并不会因为他的子接口或者实现类的初始化而初始化,只有当程序首次使用特定接口的静态常量时,才会导致该接口的初始化。