1. 类加载过程
JAVA动态类加载功能是由类加载器子系统完成。它是在运行时加载的(而不是编译时-也就是说准备使用类的时候才开始加载)。分为三个结点。加载、链接、初始化该类文件。其中,链接阶段包含验证、准备和解析三个子流程。
(1) 加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。当类被加载之后,系统为之生成一个对应的Class对象。
加载阶段完成以下三个事情:
1) 根据类的全限定名获取该类的二进制字节流。
2) 该字节流所代表的静态存储结构转换成方法区的运行时数据结构
3) 在内存中生成一个代表该类的Java.Lang.Class对象。最为方法区中这个类各种数据的访问接口。
(2) 链接
连接阶段负责把类的二进制数据合并到JRE中。
1.验证
确保加载进来的字节流满足JVM的规范。检验包含4个动作。
- 文件格式验证
- 元数据验证(是否符合JAVA语言规范)
- 字节码验证(确定程序语义合法,符合逻辑)
- 符号引用验证(确保解析正常进行)
2.准备
为静态变量分配内存并设置初始值阶段。即在方法区中分配这些变量所使用的内存空间。根据数据类型给的默认的初始值【静态变量】。而并非代码中的赋值。但是,如果是static final修饰的【静态常量】,则会直接赋值。
3.解析
虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用是指class中CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info等类型的常量。
(3) 初始化
根据程序中的赋值语句主动给类变量赋值。
关注:
1) 当有父类,父类未初始化,先初始化父类。
2) 再进行子类的初始化。
什么时候需要对类进行初始化
1) 使用new实例化该类对象的时候。
2) 使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化。
2. 类加载器
(1) 类与类加载器
类加载器:实现“通过一个类的全限定名来获取描述该类的二进制字节流”的功能的代码模块称为类加载器。类加载阶段第一部分为加载。其中需要完成3件事,第一件事就是就是类加载器来完成的。根据类的全限定名获取描述该类二进制字节流。
任何一个类,都需要加载它的加载器和这个类本身一同确定他在JVM中的唯一性。每个类加载器都拥有一个独立的类名空间。比较两个类是否相等。比如两个类来源于同一个class文件,在同一个JVM中,如果加载他们的类加载器不同,他们也不相等。
类加载器负责加载所有的类,其为所有加载到内存的类生成一个java.lang.class 对象。一旦一个类被加载到JVM中后,同一个类就不会被再次载入。一个载入JVM的类都有一个唯一的标识。
(2) 类加载器
JVM预定义三种类加载器
① 根加载器(bootstrap Class Loader)
也称之为启动类加载器。负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
② 扩展加载器(extension Class Loader)
负责加载 JAVA_HOMElibext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
③ 应用加载器(application Class Loader)
负责加载用户路径(classpath)上的类库。如果程序中没有自定义类加载器,程序默认使用这个类加载器。
(3) 双亲委派模型
当一个类加载器收到类加载任务,首先交给父类加载器去完成。因此,最终加载任务都会传递到顶层的启动类加载器。只有父类加载器无法完成加载任务的时候,子加载器才会尝试执行加载任务。
采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。