类加载的过程
1.通过编译产生.class文件
2.通过类加载器进行类加载,然后进行链接(verify验证,prepare准备,resolve解析),最后initialization初始化。
2.1 类加载(loading):在对类进行加载时,会判断是否已经加载过了,如果已经加载过了就直接可以在堆上创建实例对象。
如果没有加载过那么就轮到程序本身的类加载器(应用程序类加载器AppClassLoader)调用classLoad的方法,调用时首先它会递归调用calssLoad方法,参数是它的上一级的加载器也就是扩展类加载器(ExtClassLoader),同样扩展类加载器也会也会询问它的上级也就是引导类加载器(Bootstrap ClassLoader)是否能加载此类,如果能就上级加载,不能的话再依次往下判断。这种方法执行模式就叫做双亲委派机制,它主要的作用就是避免类被重复加载,并且可以保护程序安全,防止核心API被篡改
- 应用程序类加载器(AppClassLoade):加载用户自定义类,classpath是当前项目自己的.class路径
- 扩展类加载器(ExtClassLoader):加载/jre/lib/ext文件夹里的类,可以自己把.class文件放进去,就可以默认使用扩展类加载器进行加载
- 引导类加载器(Bootstrap ClassLoader):只加载Java API中核心的类,以java,javax,sun等开头的类
选择好类加载器后,调用findClass(String name)方法将.class文件中的字节码文件转换为二进制字节流,然后调用defineClass()方法,将字节流转换为对应的Class对象(JDK1.8后:类对象存在于堆区,但它的类信息存在于方法区),具体实现不太清楚,调用了一个native方法。
2.2链接
- 验证(verification):在loading阶段已经生成了Class对象,验证的目的就是为了确保Class的字节流中包含的信息符合JVM的要求,保证被加载类的正确性,不会影响到虚拟机的安全。主要包含:文件格式验证,元数据验证,字节码验证,符号引用验证
- 准备(preparation):这个步骤主要是为了给类变量和静态代码块设定默认初始值。注意这里不会对实例变量进行初始化。
- 解析(resolve):将常量池内的符号引用转换为直接引用的过程,这里转换的都是静态连接。
2.3初始化(initialization):执行这个类构造器方法,它不需要程序员自己定义而是由javac编译器通过收集类变量以及静态代码块中的语句合并而来,如果没有类变量,那么此方法将不会被调用。
- 一个很形象的例子
public class Test{
public static int a = 10;
{
a = 20;
}
public static void main(Stinrg[] args){
System.out.println(a);
}
}
在这个例子中很容易知道 输出的a = 20;那么看看下面一个例子呢?
public class Test{
{
a = 10;
}
public static int a = 20;
public static void main(Stinrg[] args){
System.out.println(a);
}
}
在这个例子中 同样输出的a = 20,为什么没有被声明的a变量能够直接被调用呢?
因为在之前类加载的2.2步骤:链接的准备阶段会把类变量进行初始化,这个时候a对象已经存在了,在初始化时按顺序执行静态语句,先赋值 a = 10,然后a = 20,那么最后输出的就是20了。