书成

再这样堕落下去就给我去死啊你这混蛋!!!

0%

Java类加载机制

类生命周期

 在Java 虚拟机中,类的生命周期包括7个阶段:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载。

加载

 类的加载过程主要完成3件事:

  1. 通过类的全限定名获取该类的二进制字节流,这个过程并没有规定从哪里获取,具有很大的操作空间。
  2. 将字节流的静态存储结构转为方法区或元空间的动态存储结构。
  3. 生成这个类的Class对象,作为该类各种数据的访问入口。

验证

 确保字节流中包含的信息符合虚拟机的要求,不会危害到虚拟机。主要包括文件格式验证,验证是否以魔数开头,版本号是否被虚拟机接收;元数据验证,验证类的方法,字段是否与父类矛盾;字节码验证,通过数据流与控制流分析,确定程序语义,不会危害虚拟机。

准备

 为类变量分配内存并设置默认零值,类变量是指 static 修饰的变量,如果类变量是常量,会初始化为定义的值。

解析

 将能替换的符号引用替换为直接引用,为了支持多态,有的符号引用在这个阶段不能被替换掉。

初始化

 在这个阶段虚拟机会收集赋值语句与静态语句合并为一个clinit方法执行,为类的静态赋予正确的初始值。静态语句的顺序由源文件的顺序决定。

 执行一个类的clinit方法前,会先执行父类的clinit方法(故虚拟机中第一个执行的类是Object),但是接口不需要,接口的实现类也不需要。只有接口中定义的变量使用时,才会执行接口的clinit方法。

 虚拟机会保证一个类的clinit方法在多线程环境下被正确的加锁和同步。

类初始化时机

主动引用

虚拟机规定在以下五种情况下,必须立即进行初始化:

  • 使用 new 关键字实例化对象时,访问类的静态字段,调用类的静态方法时。
  • 使用java.lang.reflect 包的方法对类进行反射调用时。
  • 初始化一个类时,它的父类还没有被初始化。
  • 虚拟机启动时,包含 main() 方法的主类会先被初始化。
  • java.lang.invoke.MethodHandle 解析结果涉及静态字段与静态方法时,对应的类应该先被初始化。

被动引用

处理主动引用外的所有引用方式都不会立即触发初始化,称为被动以用,比如:

  • 用过字类引用父类的静态字段,子类不会立即初始化
1
Sytem.out.println(SubClass.value); // value是父类的静态字段
  • 通过数组定义引用类,不会触发类的初始化,会触发数组类的初始化,数组类是虚拟机自动生成的,包含了数组的属性和方法。
1
OneClass[] os = new OneClass[100];
  • 引用一个类的常量也不会导致类的初始化,常量在编译阶段就存入常量池中。
1
System.out.println(OneClass.MAX_VALUE);

类加载器

 类加载器可以加载类,每一个类加载器都有独立的命名空间,对于任意一个类,它的类加载器和它本身确定唯一性

默认类加载器

 启动类加载器 (Bootstrap ClassLoader)负责将存放在 JRE_HOME 下的lib目录中的指定类库加载,用户无法直接引用这个加载器,如果用户需要把加载请求委派给启动类加载器,用null代替即可。

 扩展类加载器负责加载指定扩展目录下的类库。

 应用程序类加载器,也叫系统类加载器,根据程序的类路径加载Java类,是程序默认的类加载器。用户自定义的加载器默认父加载器是应用程序类加载器。

双亲委派模型

 双亲委派模型要求除了顶层的启动类加载器外,所有加载器都有父类加载器,在收到类加载请求时,优先将请求委派给父加载器,父加载器无法处理时,子加载器才会自己加载类。这样做的好处是Java类随着它的类加载器具备一种天然的优先级层次。

线程上下文类加载器

 线程上下文类加载器能打破双亲委派模型的桎梏,在一些场景十分有用,比如web服务器。通过Thread.currentThread.setContextClassLoader() 与 Thread.currentThread.getContextClassLoader() 使用线程上下文类加载器。