知识梳理-类加载

类加载

类何时被加载器加载

1. 调用类构造器.
2. 调用类中的静态变量或静态方法.
复制代码

类加载器的类型

1. 启动类加载器 BootstrapClassLoader
2. 扩展类加载器 ExtClassLoader
3. 系统加载器 AppClassLoader
复制代码

类加载过程

Untitled.png

装载

  1. ClassLoader通过一个类的全限定名(包名+类名)来查找.class文件,并生成二进制字节流. 注: 字节码的来源可以是.class文件、jar包、zip包、来源于网络的字节流.
  2. .class文件的各个部分分别解析为JVM内部特定的数据结构,并存储在方法区中.
  3. 在内存中创建一个java.lang.Class类型的对象.注:程序在运行过程中所有对该类的访问都是通过这个类对象.
JVM是什么时候转载某.class文件呢?
  • 隐式装载: 使用new等方式生成对象时,系统会隐式调用ClassLoader去装载对应的class到内存中.
  • 显示装载: 使用Class.forName()等反射方法时.

链接

验证

为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全.

  • 文件格式检验: 验证文件字节流是否符合.class文件格式的规范,并且能被当前版本的虚拟机处理.
  • 元数据检验: 对字节码描述的信息进行语义分析,以确保其描述的内容符合Java语言规范的要求.
  • 字节码检验: 通过数据流和控制流分析,确定程序语义是合法、符合逻辑的.
  • 符号引用检验: 对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验.
准备

为类中的静态变量分配内存并为其设置”0值“(默认值)

  • 基本类型(int、long、short、byte、char、boolean、float、double)的默认值为0,
  • 引用类型默认值是null.

注:如果为静态常量,则在准备阶段分配内存后直接赋值为常量值.

解析

将常量池中的符号引用转换为直接引用,也就是具体的内存地址.

  • 符号引用和直接引用的类比

    比如使用微信聊天时,在微信好友列表中,保存的是好友的名称或者别名,此时可以看做是符号引用.当我们给某个好友发消息时,会根据好友的名称找到对应好友的IP地址,此时可以看做是直接引用.

初始化

执行类构造器的<cinit>方法的过程,并真正初始化类变量,即为准备阶段时赋值为"0值"的静态变量赋予真正的值.

初始化的时机?
  • 主动引用:
    1. 虚拟机启动时,初始化包含main方法的主类.
    2. 遇到new指令创建对象实例时,如果目标对象没有被初始化则进行初始化操作.
    3. 遇到访问静态方法或静态字段的指令时,如果目标对象没有被初始化则进行初始化操作.
    4. 子类的初始化过程如果发现其父类还没有初始化,则需要先触发其父类的初始化.
    5. 使用反射时,如果目标对象没有被初始化则进行初始化操作.
    6. 第一次调用java.lang.invoke.MethodHandle实例时,需要初始化MethodHandle指向方法所在的类.
初始化类变量

在初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,即static关键字修饰的信息,而没有static修饰的语句块在实例化对象的时候才会执行.

被动引用

被动引用不会出发class的初始化.

在子类中调用父类的静态变量.

只有直接定义这个字段的类才会被初始化.通过子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化.

class对象初始化和对象的创建顺序

使用new创建一个类的实例对象时,类中的静态代码块、非静态代码块、构造函数之间的执行顺序.
静态变量/静态代码块 —> 非静态代码块 —> 构造函数

  1. 父类静态变量和静态代码块
  2. 子类静态变量和静态代码块
  3. 父类非静态成员变量和非静态代码块
  4. 父类的构造函数
  5. 子类的非静态成员变量和非静态代码块
  6. 子类的构造函数

类加载机制(双亲委派)

向上委托、向下加载
当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,只有当父类加载器找不到指定类或资源时,自身才会执行实际的加载过程.

  • ClassLoader#loadClass
             protected Class<?> loadClass(String name, boolean resolve)
                     throws ClassNotFoundException
                 {
                     synchronized (getClassLoadingLock(name)) {
                         // First, check if the class has already been loaded
                         Class<?> c = findLoadedClass(name);
                         if (c == null) {
                             long t0 = System.nanoTime();
                             try {
                                 if (parent != null) {
                                     c = parent.loadClass(name, false);
                                 } else {
                                     c = findBootstrapClassOrNull(name);
                                 }
                             } catch (ClassNotFoundException e) {
                                 // ClassNotFoundException thrown if class not found
                                 // from the non-null parent class loader
                             }
         
                             if (c == null) {
                                 // If still not found, then invoke findClass in order
                                 // to find the class.
                                 long t1 = System.nanoTime();
                                 c = findClass(name);
         
                                 // this is the defining class loader; record the stats
                                 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                                 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                                 sun.misc.PerfCounter.getFindClasses().increment();
                             }
                         }
                         if (resolve) {
                             resolveClass(c);
                         }
                         return c;
                     }
                 }
             ```
                      
    复制代码
  1. 首先会判断该Class是否已经加载,如果已加载则直接将该Class返回.
  2. 如果该Class没有加载过,则判断父类加载器是否为null,如果不是则将加载任务交给父类加载器.
  3. 如果为空则将加载任务交给启动类加载器去加载.
  4. 如果父类加载器和启动类加载器都没有加载成功,则调用当前类的findClass方法继续尝试加载.

自定义类加载器

  1. 继承ClassLoader.
  2. 重写findClass方法.
  3. 调用defineClass将字节码转换成Class对象并返回.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享