Java类加载器

1. 类加载过程

1.1. 加载

  1. 通过类名获取类的二进制字节流。可以从从本地文件、网络、数据库、自定义等方式获取。
  2. 在内存的方法区中生成一个代表这个类的java.lang.Class对象。

1.2. 链接

1.2.1. 验证

  1. 确保Class文件字节流符合JVM的要求。
  2. 主要包括对文件格式、元数据、字节码、符号引用等进行验证。

1.2.2. 准备

初始化static变量并设置初始值。初始值是数据类型的零值,例如int是0、boolean是false。

1.2.3. 解析

把编译时的符号引用(类似名称)转换为运行时的直接引用(具体地址),让程序能准确找到各种类、方法和字段。如果找不到对应目标,程序会出问题。

1.3. 初始化

  1. 执行类构造器<clinit>(),注意,不是构造函数(构造函数是“对象”初始化的时候用的)。
  2. 为static变量赋值,也就是程序员在代码中定义的初始值。
  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

类比记忆:

用组装一辆自行车的过程来类比 Java 类加载的各个阶段:

一、加载(买回来)

买一堆组装自行车的零件回来。就像类加载器获取类的二进制字节流,我们先要去寻找车架、车轮、车把等各种零件。

二、链接(掏出来)

  1. 验证(看买的是不是合适):检查零件是否符合要求。就像类加载中验证Class文件的字节流是否符合JVM要求。
  2. 准备(掏出来放到地上):将零件从塑料袋里拿出来,摆好。就像为类变量分配内存并设置初始值。
  3. 解析(看图纸):确定各个零件的安装位置和安装方式。比如知道车把要安装在哪个部位、链条如何连接。就像类加载中,将符号引用转换为直接引用,明确各种资源的具体使用方式。

三、初始化(组装)

正式组装自行车。按步骤将各个零件组装在一起,使自行车能够正常使用。就像类加载中,执行类构造器方法,为类变量赋予指定的初始值。

只有当有人准备骑这辆车(主动使用类),才会触发整个组装过程。如果只是看看自行车的图片(非主动使用),则不会触发组装过程。

2. 类加载器

类加载器是用于加载字节码到JVM中,代表某个类的Class对象。

  1. 数组和基本类型没有类加载器,由JVM直接创建
  2. 其他类型的对象,都有类加载器

3. 三种类加载器

3.1. 启动类加载器(Bootstrap ClassLoader)

  1. JVM内置的,C/C++实现,加载JVM自身运行所需的核心库,如Java安装目录下的jre/lib目录中的文件。
  2. java.lang.Object、java.lang.String等核心类都是由启动类加载器加载的。
  3. Java代码中不能获得启动类加载器的实例,它对开发人员是不可见的,代码中获取classLoader的时候如果获取到null,就说明是启动类加载器加载的。

3.2. 扩展类加载器(Extension ClassLoader)

  1. 加载Java的扩展库,如Java安装目录下jre/lib/ext或者系统属性java.ext.dirs指定的目录。
  2. 扩展类加载器是Java实现的,它的父加载器是启动类加载器。

3.3. 应用程序类加载器(Application ClassLoader)

  1. 也称为系统类加载器,加载应用程序类路径(classpath)下的类。通常是开发人员编写的Java代码所对应的类。
  2. 应用程序类加载器的父加载器是扩展类加载器。

3.4. 自定义类加载器

  1. 开发人员可编写自己的类加载器,以实现灵活的类加载策略,如加解密字节码、热部署(运行时替换类)、远程加载(加载网络上的类)、隔离类(避免冲突和安全)。
  2. 若未指定,自定义类加载器的父加载器默认是应用程序类加载器。
  3. 可以指定自定义类加载器是扩展、应用程序、其他自定义类加载器作为父加载器。
  4. 不能指定启动类加载器作为父加载器。

启动核心无法动,扩展加载扩展类。应用加载开发类,自定灵活随便搞。

 

以大型超市为记忆宫殿:

启动:超市仓库,存放核心货物,对应加载核心类库,神秘不可见。

扩展:进口商品区,带来特色商品,如加载扩展类库增添功能。

应用程序:日常用品区,顾客常光顾,如同加载日常开发的类。

自定义:特色定制区,根据特殊需求定制商品,对应灵活创新的自定义加载。

4. 双亲委派模式

当类加载器收到加载请求时,先将请求委派给父类加载器,父类加载器无法加载时自己才尝试加载。

这样确保了核心类库的安全稳定,避免类重复加载,实现类的隔离。

5. 打破双亲委派

打破双亲委派模型应该谨慎使用,因为这可能会导致一些潜在的问题,如类的重复加载、类的不一致性等。在大多数情况下,双亲委派模型是一种安全和可靠的类加载机制,应该尽量遵循。

方法1. 自定义类加载器

在自定义类加载器中,可以重写 loadClass方法来实现自己的类加载逻辑,从而打破双亲委派模型

public class CustomClassLoader extends ClassLoader {

    @Override

    public Class<?> loadClass(String name) throws ClassNotFoundException {

        try {

            // 先尝试自己去加载类

            return findClass(name);

        } catch (ClassNotFoundException e) {

            // 如果自己加载失败,再调用父类的 loadClass 方法

            return super.loadClass(name);

        }

    }

}

方法2. 用线程上下文类加载器

线程上下文类加载器可以在运行时动态地设置为特定的类加载器,从而使得在特定的代码区域中使用这个类加载器来加载类,而不是遵循双亲委派模型。

public class ClassLoaderExample {

    public static void main(String[] args) throws Exception {

        // 获取当前线程的上下文类加载器

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

        // 设置上下文类加载器为自定义类加载器

        Thread.currentThread().setContextClassLoader(new CustomClassLoader());

        // 使用上下文类加载器加载类

        Class<?> clazz = contextClassLoader.loadClass("SomeClass");

    }

}

6. tomcat为啥要打破双亲委派?

tomcat上可以部署多个应用,如果Tomcat不打破双亲委派,则所有应用的类都被同一个加载器管着,会有两个问题:

  1. 类冲突:比如两个应用都要用了同一个jar但版本不同,就会冲突;
  2. 做不到热部署:应用改了代码后不能快速重新加载新的类,这样开发和部署起来就不方便。

 

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值