JavaRush /Java 博客 /Random-ZH /类是如何加载到 JVM 中的
Aleksandr Zimin
第 1 级
Санкт-Петербург

类是如何加载到 JVM 中的

已在 Random-ZH 群组中发布
在完成了程序员工作中最困难的部分并编写了“Hello World 2.0”应用程序后,剩下的就是组装分发工具包并将其传输给客户,或者至少传输给测试服务。在发行版中,一切都按其应有的方式进行,当我们启动程序时,Java 虚拟机就出现了。众所周知,虚拟机以字节码的形式读取类文件中提供的命令,并将它们转换为处理器的指令。我建议了解一下字节码进入虚拟机的方案。

类加载器

它用于向 JVM 提供编译后的字节码,该字节码通常存储在扩展名为 的文件中.class,但也可以从其他来源获取,例如通过网络下载或由应用程序本身生成。 类是如何加载到 JVM 中的 - 1根据Java SE规范,为了让代码在JVM中运行,需要完成三个步骤:
  • 从资源加载字节码并创建类的实例Class

    这包括在先前加载的类中搜索请求的类,获取用于加载的字节码并检查其正确性,创建该类的实例Class(以便在运行时使用它)以及加载父类。如果父类和接口尚未加载,则认为该类未加载。

  • 绑定(或链接)

    根据规范,这个阶段又分为三个阶段:

    • 验证,检查接收到的字节码的正确性。
    • 准备工作,为静态字段分配 RAM 并使用默认值初始化它们(在这种情况下,显式初始化(​​如果有)已经在初始化阶段发生)。
    • 解析,类型、字段和方法的符号链接解析。
  • 初始化接收到的对象

    在这里,与前面的段落不同,一切似乎都清楚应该发生什么。当然,准确理解这是如何发生的会很有趣。

所有这些步骤均按以下要求顺序执行:
  • 在链接之前必须完全加载该类。
  • 一个类在初始化之前必须经过充分的测试和准备。
  • 链接解析错误在程序执行期间发生,即使它们是在链接阶段检测到的。
如您所知,Java 实现了类的延迟(或惰性)加载。这意味着在应用程序遇到对它们的显式引用之前,不会执行已加载类的引用字段的类的加载。换句话说,解析符号链接是可选的,默认情况下不会发生。然而,JVM 实现也可以使用能量类加载,即 必须立即考虑所有符号链接。正是出于这一点,最后一个要求才适用。还值得注意的是,符号链接的解析不依赖于类加载的任何阶段。一般来说,每个阶段都值得好好研究;让我们尝试找出第一个阶段,即加载字节码。

Java 加载器的类型

Java 中有三个标准加载器,每个加载器从特定位置加载一个类:
  1. Bootstrap是一个基本的加载器,也称为Primordial ClassLoader。

    从 rt.jar 存档加载标准 JDK 类

  2. 扩展类加载器——扩展加载器。

    加载扩展类,默认位于 jre/lib/ext 目录中,但可以通过 java.ext.dirs 系统属性设置

  3. 系统类加载器——系统加载器。

    加载 CLASSPATH 环境变量中定义的应用程序类

Java 使用类加载器的层次结构,其中根当然是基础类加载器。接下来是扩展加载器,然后是系统加载器。当然,每个加载器都会存储一个指向父加载器的指针,以便在它本身无法执行此操作的情况下能够将加载委托给它。

抽象类类加载器

除了基类之外,每个加载器都是抽象类的后代java.lang.ClassLoader。例如,扩展加载器的实现是 类sun.misc.Launcher$ExtClassLoader,系统加载器是sun.misc.Launcher$AppClassLoader。基本加载器是本机的,其实现包含在 JVM 中。任何扩展的类都java.lang.ClassLoader可以提供自己的方式来加载二十一点和这些相同的类。为此,需要重新定义相应的方法,目前我只能粗浅地考虑一下,因为 我没有详细了解这个问题。他们来了:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)少数公共方法之一,它是加载类的入口点。它的实现归结为调用另一个受保护的方法loadClass(String name, boolean resolve),该方法需要被重写。如果您查看此受保护方法的 Javadoc,您可以理解如下内容:提供两个参数作为输入。一种是需要加载的类的二进制名称(或完全限定的类名称)。类名是用所有包的列表指定的。第二个参数是一个标志,确定是否需要符号链接解析。默认情况下它是false,这意味着使用延迟类加载。此外,根据文档,在该方法的默认实现中,进行了一个调用findLoadedClass(String name),该调用检查先前是否已加载该类,如果是,则返回对该类的引用。否则,将调用父加载器的类加载方法。如果没有一个加载器可以找到已加载的类,则每个加载器都会按照相反的顺序尝试查找并加载该类,从而覆盖findClass(String name). 这将在“类加载方案”一章中更详细地讨论。最后,最后但并非最不重要的一点是,在类加载后,根据解析标志,将决定是否通过符号链接加载类。一个明显的例子是,可以在类加载阶段调用Resolution阶段。因此,通过扩展类ClassLoader并重写其方法,自定义加载器可以实现其自己的逻辑,以将字节码传递到虚拟机。Java 还支持“当前”类加载器的概念。当前加载器是加载当前正在执行的类的加载器。每个类都知道它是用哪个加载器加载的,您可以通过调用其String.class.getClassLoader(). 对于所有应用程序类,“当前”加载器通常是系统加载器。

类加载的三个原则

  • 代表团

    加载类的请求将传递给父加载器,并且仅当父加载器无法找到并加载该类时,才会尝试加载该类本身。这种方法允许您使用尽可能接近基类的加载器来加载类。这实现了最大的班级可见性。每个加载器都会保留其加载的类的记录,并将它们放置在其缓存中。这些类的集合称为范围。

  • 能见度

    加载器只能看到“它的”类和“父类”的类,并且不知道其“子类”加载的类。

  • 独特性

    一个类只能加载一次。委托机制确保启动类加载的加载器不会重载先前加载到 JVM 中的类。

因此,在编写引导加载程序时,开发人员应该遵循这三个原则。

类加载方案

当发生加载类的调用时,会在当前加载器已加载的类的缓存中搜索该类。如果之前尚未加载所需的类,则委托原则会将控制权转移给位于层次结构中更高一级的父加载器。父加载器还会尝试在其缓存中查找所需的类。如果该类已被加载并且加载器知道其位置,则将Class返回该类的对象。如果没有,搜索将继续,直到到达基本引导加载程序。如果基加载器没有所需类的信息(即尚未加载),则将在给定加载器知道的类的位置中搜索该类的字节码,如果该类不能加载后,控制权将返回到子加载器,子加载器将尝试从已知的源加载。如上所述,基本加载器的类位置是 rt.jar 库,扩展加载器的类位置是 jre/lib/ext 扩展的目录,系统类的位置是 CLASSPATH,用户类的位置可以是不同的。因此,加载类的进度是相反的方向——从根加载器到当前加载器。当找到类的字节码时,该类将被加载到 JVM 中并获得该类型的实例Class。正如您所看到的,所描述的加载方案与上述方法的实现类似loadClass(String name)。下面你可以在图中看到这个图。
类是如何加载到 JVM 中的 - 2

作为结论

在学习语言的第一步中,没有特别需要了解Java中类是如何加载的,但是了解这些基本原理将有助于您在遇到诸如 或 之类的错误时避免ClassNotFoundException绝望NoClassDefFoundError。好吧,或者至少大致了解问题的根源是什么。ClassNotFoundException因此,当在程序执行期间动态加载类时,如果加载器无法在缓存中或沿着类路径找到所需的类,则会发生异常。但该错误NoClassDefFoundError更为严重,当所需的类在编译期间可用但在程序执行期间不可见时,就会发生该错误。如果程序忘记包含它使用的库,就会发生这种情况。嗯,了解您在工作中使用的工具的结构原理(不一定是清楚而详细地深入了解)这一事实本身就可以让您更清楚地理解该机制内部发生的过程,这在转向,导致自信地使用这个工具。

来源

Java 中的类加载器如何工作 总体而言,这是一个非常有用的源,具有易于理解的信息表示形式。 加载类,ClassLoader 这是一篇相当长的文章,但重点是如何使用这些相同的加载器实现您自己的实现。 ClassLoader:类的动态加载 不幸的是,这个资源现在不可用,但是我在那里找到了最容易理解的类加载方案图,所以我忍不住添加它。 Java SE 规范:第 5 章:加载、链接和初始化
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION