在Java中,类加载器通过以下步骤找到和加载类文件:
  1. 加载(Loading):

    • 当程序引用一个类时,如果该类还没有被加载到JVM中,类加载器就会被调用来加载该类。

    • 类加载器首先检查这个类是否已经被加载。如果没有,它将按照指定的搜索路径寻找对应的.class文件。

    • 对于Bootstrap类加载器,它主要负责加载$JAVA_HOME/jre/lib目录下或者由-Xbootclasspath参数指定的路径中的核心类库。

    • Extension类加载器加载$JAVA_HOME/jre/lib/ext目录下或由系统属性java.ext.dirs指定位置的类库。

    • System/Application类加载器加载环境变量CLASSPATH或系统属性java.class.path指定的路径、类路径参数(-cp/-classpath)以及应用程序的类路径下的类。

  2. 链接(Linking):

    • 验证(Verification): 确保加载的类符合JVM规范,不会危害到运行时环境。

    • 准备(Preparation): JVM为类变量分配内存,并设置默认初始值。

    • 解析(Resolution): 将类中的符号引用转换成直接引用。

  3. 初始化(Initialization):

    • 在类被第一次使用时,比如创建实例、调用类的静态方法、使用类或接口的静态字段时(不包括定义常量的那种静态字段),JVM负责初始化类。

    • 初始化涉及执行类初始化器,即运行类中的静态初始化块和静态字段的赋值操作。

提醒这个过程是按需加载,即当需要使用某个类时,JVM才会加载它,而不是一开始就加载所有的类。这种方式可以减少资源消耗,提高性能。

双亲委派模型(Parent Delegation Model):

在上述过程中,Java采用双亲委派模型来加载类。这意味着:
  • 当一个类加载器尝试加载某个类时,它首先会代理给其父类加载器进行加载。

  • 如果父类加载器无法完成这个任务(它并没有找到对应的类),子类加载器才会尝试自己加载这个类。

  • 这个过程从Bootstrap类加载器开始,然后是Extension类加载器,最后是System/Application类加载器。

这种模型可以保护Java核心库的类不被用户自定义的类所替代,有助于保证Java平台的安全性。同时,它也避免了类的多次加载,因为一旦类被加载,它就会在加载器的缓存中。

例子:

假设我们有一个名为 com.example.MyClass 的类,并且它不是 Java 标准库的一部分,而是用户定义的,并打包在 myclasses.jar 文件中。

当你的应用程序运行并第一次引用 MyClass 时,以下是会发生的事情:

  1. 系统类加载器会收到一个请求来加载 MyClass。

  2. 遵循委派模型,系统类加载器首先会委派给扩展类加载器,扩展类加载器又会委派给启动类加载器。

  3. 启动类加载器会搜索 JVM 核心库,但不会找到 MyClass,因为它不是核心库的一部分,于是返回并告诉扩展类加载器未找到该类。

  4. 扩展类加载器也不能在其搜索范围内找到 MyClass,同样返回并告知系统类加载器。

  5. 系统类加载器最后会在用户的类路径(假设 myclasses.jar 已经添加到了类路径中)查找 MyClass.class 文件。

  6. 系统类加载器找到并加载 MyClass,然后进行链接和初始化。

  7. MyClass 现在可用于应用程序。

通过这个过程,Java 能够动态地加载类,并确保了类加载器的层级关系,防止了用户代码替换核心库中的类。

本篇文章来源于微信公众号: 互联网面试小帮手



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-05-13