虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是类加载机制。
类加载的时机:
下面5中情况必须立即对类进行”初始化”(加载、验证、准备自然需要在此之前开始):
- 遇到new、getstatic、putstatic、invokestatic这4个字节码指令时。即使用new实例化对象、读取类的静态字段、调用类的静态方法的时候。
- 使用java.lang.reflect包的方法进行反射调用时,如果类没有进行过初始化,需要先出发初始化。
- 初始化一个类时,发现其父类还没进行过初始化,需要先出发父类的初始化。
- 虚拟机启动时,需要指定一个要执行的主类先进行初始化(包含main方法的那个类)。
- 当使用动态语言支持时,java.lang.invoke.MethodHandle实例解析结果的方法句柄所对应的类没有进行过初始化时。
类加载过程
- 加载
完成3件事情:- 通过类的全限定名获得此类的二进制字节流
- 将二进制流代表的静态结构转换为方法区的运行时结构
- 内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口
- 验证
连接阶段的第一步,为了确保Class文件的字节流中包含的信息符合虚拟机的要求。- 文件格式验证:是否以魔数开头、主次版本号是否在当前虚拟机处理范围内、常量池的常量中是否有不被支持的常量类型等。
- 元数据验证:这个类是否有父类、父类是否继承了不允许被继承的类、如果不是抽象类是否实现了父类的所有方法等。
- 字节码验证:保证方法体的类型转换时有效的、保证跳转指令不会跳转到方法体之外等。
- 符号引用验证:通过字符串描述的全限定名是否能找到对应的类、访问权限是否可以被当前的类访问等。
- 准备
正式为类变量分配内存并设置类变量初始值的阶段。 - 解析
将常量池内的符号引用替换为直接引用的过程。- 符号引用:描述所引用目标的字面量,虚拟机间通用,因为描述在class文件中。可以定位到目标即可,目标不需已经存在在内存中。
- 直接引用:可以是指向目标的指针、偏移量或间接的句柄。和虚拟机的内存布局有关,将符号引用翻译过来的直接引用,在各虚拟机中可能不同。翻译成直接饮用后,指向的目标必须已经存在在内存。
- 初始化
准备阶段已经为变量赋初始化值,这个阶段是按照程序员的计划区初始化类变量和其他资源。就是执行类构造器()方法的过程。
类加载器
比较两个类是否“相等”,只有在这两个类是被同一个类加载器加载的前提下才有意义,否则,即使这两个类来自同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类就必定不相等。
双亲委派模型:
- 启动类加载器:加载存放在%JAVA_HOME%\lib目录下的类库
- 扩展类加载器:加载%JAVA_HOME%\lib\ext目录下的类库,开发者可以直接使用扩展类加载器
-
应用程序类加载器:也成为系统类加载器。记载用户类路径(ClassPath)上指定的类库
双亲委派模型的工作过程:当一个加载器接到一个加载请求时,先不处理,而是交给父加载器处理,层层上交,直到启动类加载器。父类不能加载时,才会让子类加载。好处是类随着加载器具有了优先级。比如自定义一个java.lang.Object类,加载时会先交到启动加载器,启动加载器会加载java中的Object,自定义的那个永远无法被加载。这就防止了重名导致的高优先级的类无法使用。