类加载 类加载器 详细

本文详细介绍了Java类的生命周期,包括加载、验证、准备、解析、初始化、使用和卸载阶段。重点讲解了类加载器的工作原理,尤其是双亲委派模型,以及其在防止基础类篡改、确保类唯一性等方面的作用。同时,讨论了自定义类加载器、线程上下文加载器和其在JNDI服务、模块热部署等场景的应用。
摘要由CSDN通过智能技术生成

类的生命周期

加载、验证、准备、解析、初始化、使用、卸载。
java支持动态语言绑定,解析可能在初始化之后。其他的顺序是固定的。

类加载的过程

加载
  • 根据类的全限定类名获取定义类的二进制字节流
  • 将二进制字节流代表的类静态存储结构转化为方法区的数据结构
  • 在堆中生成一个java.lang.Class对象封装方法区的数据结构,作为方法区的数据结构的入口
验证
  • 文件格式验证:验证字节流是否符合class文件规范。是否以魔数0XCAFEBABY开头,主次版本号是否在jvm的处理范围,常量是否有不存在的类型等。
  • 元数据验证:对字节流描述的信息进行分析,验证是否符合java语言语法规范。是否有父类,继承链是否正确,与父类的字段是否有冲突,抽象父类和接口的方法是否都实现等。
  • 字节流验证:分析数据流和控制,分析语义是否合法、符合逻辑。特别是对方法体进行验证,例如验证跳转指令是否跳转到方法体之上。
  • 符号引用验证:确保之后的解析阶段可以将符号引用转换为直接引用。主要是验证是否有无法访问到类、接口、字段、方法等。
准备

给类静态变量分配内存,并赋予默认零值。被final修饰的静态变量在准备阶段初始化。

解析

将常量池中的符号引用转换为直接引用。主要是类、接口、方法、字段等。符号引用就是字面常量符号。直接引用,能直接指向目标,偏移量或句柄。

初始化

执行clinit(),为静态变量赋予正确的初始值。若类无静态变量声明和静态代码块,该类可无clinit()。若接口无静态变量声明,则该接口可无clinit()
clinit()是按顺序收集静态变量声明和静态代码块得到的。jvm会保证clinit方法线程安全,相当于被synchronized修饰的静态方法。

类初始化时机

  • 访问静态变量、赋值给静态变量、调用静态方法。注意不包括访问静态常量。
  • new类的实例、Class对象的newInstance
  • 反射Class.forName("")
  • 初始化子类前会先初始化父类。
  • 启动类会首先被jvm加载
  • MethodHandle和VarHandle可以看作是轻量级的反射机制,调用这两个指令必须先对要调用的类初始化
  • 使用了default关键字的接口在实现类初始化时,该接口也要初始化

xxclassLoader的loadClass不会导致类初始化。类.class获取Class对象也只是不会导致类初始化。

类的唯一性

jvm中类由全限定名和类加载器唯一确定。

类加载器

启动类加载器(BootstrapClassLoader)
c++实现,为jvm的一部分。加载jre/lib下的几个核心jar包,例如rt.jar等。尝试获取启动类加载器的话会返回null。

扩展类加载器(ExtClassLoader)
继承自java.lang.ClassLoader,加载jre/lib/ext和java.ext.dirs系统属性指定的路径下的类。
应用类加载器(系统类加载器 AppClassLoader)
继承自java.lang.ClassLoader,加载java命令指定的-classpath、系统属性java.class.path、环境变量CLASSPATH下的类。ClassLoader.getSystemClassLoader()返回应用类加载器。应用类加载器是默认加载器。parent为扩展类加载器。
自定义类加载器

  1. 继承java.lang.ClassLoader,默认parent为应用类加载器。
  2. 实现findClass方法

双亲委派模型
类加载请求到达类加载器时,先委托给父类加载器加载,若父类加载器还有父类加载器则递归向上委托,最终委托给启动类加载器。当父类加载器在其搜索范围找不到类时才由子类加载器自己加载。
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) {
                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);


                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

双亲委派模型好处

  • 使基础类得到统一,防止基础类被篡改。
  • 确保只有一份Class对象。

类加载机制

  • 全盘负责。某个类加载器负责加载一个类时,该类加载器也负责加载类中引用的类。
  • 双亲委派。
  • 缓存机制。被加载过的类的Class对象会被缓存起来。获取Class对象时,先在缓存中查找是否有,若无才去加载。这就是修改class后,需要重启jvm才能生效的原因。

线程上下文加载器
线程上下文加载器用于加载线程中运行代码中引用的类。默认的线程上下文加载器为应用类加载器。默认子线程会继承父线程的线程上下文加载器。通过Thread对象的setContextClassLoader方法可以设置线程上下文加载器。

  • JNDI服务通过线程上下文加载器加载SPI(Service Provider Interface)的代码,使得父类加载器的类加载请求委托给了子类加载器。也就是逆向打通了双亲委派模型,这实际上违背了双亲委派模型。
  • 代码热替换(HotSwap)、模块热部署(Hot Deployment)等,OSGi实现模块热部署的关键是自定义的类加载器机制的实现。每个程序模块(bundle)都有一个类加载器,更换bundle时,将bundle和类加载器一同替换掉以实现热部署。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值