第一阶段:装载阶段 →类加载器*
启动类加载器(又称为根加载器/引导类加载器)Boostrap ClassLoader(顶级父类,该类加载器是由 C 语言代码进行开发的,如果一个类的类加载器是 Bootstrap ClassLoader,那么该类的 getClassLoader()方法返回 null。) 启动类加载器加载的是jre和jre/lib目录下的核心库,具体路径要看你的jre安装在哪里(主要是rt.jar和部分.jar)
⬇
扩展类加载器 Extention ClassLoader(父类是启动类加载器) 扩展类加载器加载的是jre/lib/ext目录下的扩展包
⬇
应用类加载 Application ClassLoader(父类是扩展类加载器) 当前java工程的bin目录,也就是我们自己的Java代码编译成的class文件。(用来加载应用类路径(CLASSPATH)下的 class文件)
加载一个类:双亲(parent)委派模型
* 双亲(parent)委派模型工作过程:
从子类到父类依次查找之前是否加载过这个类,如果加载过,返回加载后产物(Class对象)。如果当前类加载器没有加载过这个,向父类继续查找。如果都没有加载过,那么从父类到子类依次尝试加载。继续失败就会抛出一个异常 ClassNotFoundException
双亲(parent)委派模型的优点:
(1)安全性,避免用户自己编写的类动态替换 Java 的一些核心类。如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替代 Java 核心 API 中定义的类。例如:如果黑客将“病毒代码”植入到自定义的 String 类当中,随后类加载器将自定义的 String 类加载到 JVM 上,那么此时就会对 JVM 产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为 String 类已经在启动时就被引导类加载器进行了加载。
(2)避免类的重复加载,因为 JVM 判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的 class 文件被不同的类加载器加载得到的结果就是两个不同的类
装载成功的产物:当前类的Class对象(保存当前类的类信息),在Java堆上保存(jdk>=1.6)
类加载时机:
- new,静态属性,静态方法调用
- People.class 获取Class对象
- 加载子类时需要提前加载父类
- 启动JVM,main函数所在的类需要加载
第二阶段:链接阶段(三个小阶段)
验证阶段:符合虚拟机要求,不能危害虚拟机安全(比如是否有病毒),文件格式,主次版本号,等等···
准备阶段:给静态变量开辟内存并赋类型默认值。
static int count = 10,
//count开辟四个字节,赋值0
解析阶段:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
String str = “hello”;
//str 保存的是hello这个常量在磁盘上的地址 (符号引用)
//str 保存的是在JVM上的地址 (直接引用)
第三阶段:初始化阶段
给静态变量赋值 count = 10