在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。
类加载机制指的是将这些.class文件中的二进制数据读入到内存中,并对数据进行校验,解析和初始化。最终,每一个类都会在方法区保存一份它的元数据,在堆中创建一个与之对应的Class对象。
java的类加载机制主要分为七个步骤
加载,校验,准备,解析,初始化,使用,卸载
加载:
1.根据类的全名找到对应的class文件
2.将class文件中的二进制文件读取出来,在方法区生成对应的数据结构
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
校验:
Class文件中的内容是字节码,这些内容可以由任何途径产出,验证阶段的目的是保证文件内容里的字节流符合Java虚拟机规范,且这些内容信息运行后不会危害虚拟机自身的安全。
准备:
这个阶段,类的静态字段信息(即使用 static 修饰过的变量)会得到内存分配,并且设置为初始值。
例如:public static int value = 3 类变量 value 在准备阶段设置的初始值是 0,不是
3。把value赋值为3的 putstatic 指令是在程序编译后,存放于类构造器 () 方法中的,所以把 value
赋值为 3 的动作将在初始化阶段才会执行。 当使用 final 修饰后:public static final int value = 3
类变量 value 在准备阶段设置的初始值是 3,不是 0。
解析:
这个阶段,虚拟机会把这个Class文件中,常量池内的符号引用转换为直接引用。主要解析的是 类或接口、字段、类方法、接口方法、方法类型、方法句柄等符号引用。我们可以把解析阶段中,符号引用转换为直接引用的过程,理解为当前加载的这个类,和它所引用的类,正式进行“连接“的过程。
初始化:
初始化的过程,就是执行类构造器 ()方法的过程。
当初始化完成之后,类中static修饰的变量会赋予程序员实际定义的“值”,同时类中如果存在static代码块,也会执行这个静态代码块里面的代码。
类加载器
还记得在加载阶段,通过类的全限定名,获取该类字节流数据的这个动作么,类加载器就是用来实现这个动作的。
双亲委派模型:
双亲委派模型是Java类加载器的一种工作机制,它是一种层次化的类加载器结构,其中每个类加载器都有一个父类加载器。当某个类加载器需要加载一个类时,它首先会将这个请求委派给自己的父类加载器去完成。如果父类加载器无法完成这个加载请求,子类加载器才会尝试自己去加载这个类。这个过程一直持续到顶层的启动类加载器,如果启动类加载器无法完成加载请求,就会抛出ClassNotFoundException异常。
双亲委派模型的具体工作过程如下:
-
当Java虚拟机启动时,会先由启动类加载器(Bootstrap ClassLoader)加载核心类库(如java.lang包),这些类库是虚拟机运行所必需的类。
-
当应用程序需要加载某个类时,它首先会由系统类加载器(System ClassLoader)去尝试加载这个类。如果系统类加载器无法完成这个加载请求,它会将请求委派给它的父类加载器——扩展类加载器(Extension ClassLoader)去完成。
-
如果扩展类加载器也无法完成加载请求,它会将请求继续委派给它的父类加载器——启动类加载器去完成。启动类加载器是虚拟机内置的类加载器,它是类加载器层次结构的最顶层,所有其它类加载器的父类加载器都是它。
-
如果启动类加载器仍然无法完成加载请求,就会抛出ClassNotFoundException异常。
采用双亲委派模型的好处是可以有效避免类的重复加载,同时也可以保护Java核心类库不受恶意代码的侵害。当一个类被加载时,它会优先从父类加载器的缓存中查找是否已经加载过。如果已经加载过,就直接返回缓存中的类对象,避免了重复加载。如果没有加载过,就会委派给父类加载器去完成加载请求,父类加载器也会先从它的缓存中查找是否已经加载过,以此类推,直到找到顶层的启动类加载器为止。这样可以保证类的唯一性和一致性,避免出现类的版本冲突和安全问题。
总之,双亲委派模型是Java类加载器的一种工作机制,它采用层次化的类加载器结构,并将类加载请求委派给父类加载器去完成,以此保证类的唯一性和一致性。