JVM成神之路: 类加载机制

1. 类加载是什么?

  • 类加载是指将 Java 程序中描述的各类信息(即 .class 文件中的字节码)加载到 JVM(Java 虚拟机)内存中并进行处理的过程。JVM 通过类加载器将 .class 文件的二进制数据加载到内存中,然后对这些数据进行校验、解析和初始化,最终形成可以直接在 JVM 中使用的 Java 类型。
  • 与编译时需要静态链接的语言不同(c++),Java 的类型加载、连接(linking)、和初始化都是在运行时完成的。这种动态加载和连接机制虽然增加了一些性能开销,但也提供了极高的扩展性,使得 Java 可以动态扩展、热加载类等。
  • 类的生命周期从被加载到虚拟机内存开始,到从内存中卸载结束,经历以下七个阶段:加载 (Loading),验证 (Verification),准备 (Preparation),解析 (Resolution),初始化 (Initialization),使用 (Usage),卸载 (Unloading)
  • 其中,验证准备解析被统称为 连接 (Linking) 阶段。加载、验证、准备、初始化的顺序是固定的,但解析阶段可以在初始化之后进行,这是为了支持 Java 的动态绑定。

2. 类加载的过程

  1. 加载 (Loading)

    • 获取类的二进制字节流:虚拟机通过类的全限定名(例如 java.lang.String)来获取对应的 .class 文件的二进制字节流,这个字节流可以来自文件系统、网络、数据库等任何地方。
    • 转换为方法区的运行时数据结构:将字节流所代表的静态存储结构转化为 JVM 方法区(JDK 1.8 及以后为元空间 Metaspace)中的运行时数据结构。
    • 在内存中生成 Class 对象:在堆内存中生成一个代表这个类的 java.lang.Class 对象,这个对象作为方法区中类数据的访问入口。
  2. 验证 (Verification)

    • 文件格式验证:检查 .class 文件的格式是否符合 JVM 规范,比如文件头是否以 0xCAFEBABE 开头等。
    • 元数据验证:对类的元数据进行语义检查,比如类是否有父类,父类是否被加载等。
    • 字节码验证:确保程序中的代码不会做出危害虚拟机安全的行为,例如方法的局部变量使用是否正确、操作数栈不会溢出等。
    • 符号引用验证:确保符号引用能正确解析,确保在运行时符号引用能够找到所引用的目标。
  3. 准备 (Preparation)

    • 为类的静态变量分配内存并设置零值:在方法区中为类变量分配内存,并将其初始化为默认值(如 int 型为 0,boolean 型为 false 等)。
    • 处理 final 修饰的变量:如果类变量被 final 修饰,且在编译期可以确定其值,那么在准备阶段就会被初始化为指定的值。
  4. 解析 (Resolution)

    • 将常量池中的符号引用转换为直接引用。
    • 符号引用:使用一组符号来描述引用目标,可以是任何形式的字面量,引用目标不一定已经加载到虚拟机内存中。
    • 直接引用:指向内存中某个具体地址的指针或相对偏移量,引用的目标必须已经在内存中存在。解析是符号引用到直接引用的过程。
  5. 初始化 (Initialization)

    • 执行类构造器 <clinit> 方法:这是初始化阶段,JVM 执行类的静态初始化块(静态代码块)和类变量的初始化语句。<clinit> 方法是编译器自动收集类的所有静态语句块和静态变量的初始化语句合成的。
    • 正式执行类中的代码:在此阶段,类的所有静态变量和静态块都会按顺序执行,这是 JVM 的一个 STW(Stop-The-World)操作,即所有用户线程在该阶段都会暂停执行。

3. 有哪些类加载器?

  • 自 JDK 1.2 以来,Java 一直采用三级类加载器体系。以下是常见的类加载器:
  1. 启动类加载器(Bootstrap ClassLoader):

    • 描述: 启动类加载器是 JVM 自带的类加载器,负责加载 Java 的核心类库,如 java.lang.Objectjava.lang.Stringjava.util.* 等。
    • 特点: 它是用本地代码(C/C++等)实现的,并且是 JVM 启动时创建的,无法在 Java 程序中直接引用。如果需要将类加载委派给启动类加载器,可以用 null 来表示。
    • 加载范围: 加载 JDK 自带的核心类库(通常位于 JRE 的 lib 目录下,或由 -Xbootclasspath 参数指定的路径)。
  2. 平台类加载器(Platform ClassLoader):

    • 描述: 从 JDK 9 开始,平台类加载器取代了扩展类加载器(Extension ClassLoader),用于加载 Java 平台的一些扩展库。
    • 特点: 主要负责加载 JDK 提供的扩展功能类库,如 XML 解析、加密、压缩等模块相关的类库。
    • 加载范围: 加载 Java 平台的扩展库,通常位于 lib/ext 目录或其他指定路径。
  3. 应用类加载器(Application ClassLoader):

    • 描述: 也称为系统类加载器(System ClassLoader),负责加载用户类路径(Classpath)上的类库。
    • 特点: 可以在代码中直接使用 ClassLoader.getSystemClassLoader() 获取。应用类加载器是大多数 Java 应用的默认类加载器,如果没有指定自定义类加载器,通常由它来加载用户定义的类。
    • 加载范围: 加载用户定义的类路径中的类库,通常是 classpath 路径上的内容(如 -cp 参数指定的路径)。
  • 此外,还可以定义自定义类加载器,通过继承 ClassLoader 类并重写 findClass 方法来实现,通常用于加载用户自定义的类或动态加载。

4. 双亲委派模型是什么?

  • 双亲委派模型(Parent Delegation Model) 是一种类加载器的工作机制,确保类的加载过程具有安全性和一致性。
  • 工作机制:

    • 当一个类加载器收到类加载请求时,它不会自己直接去加载该类,而是将请求委派给其父类加载器去尝试加载。
    • 这一委派过程是递归的,最终会将加载请求传递到顶层的启动类加载器(Bootstrap ClassLoader)。
    • 如果启动类加载器无法加载这个类(例如,类不在它的加载范围内),才会逐层返回给下层的子加载器,让子加载器尝试自己加载该类。
  • 优点:

    • 确保安全性和稳定性: 这种机制确保了 Java 核心类库(如 java.lang.Object)不会被用户自定义类库覆盖,因为这些核心类首先会被启动类加载器加载,一旦加载成功,后续的类加载请求都会使用这个已经加载的类。
    • 避免重复加载: 同一个类只会被加载一次,确保类的一致性和节省内存空间。

5. 如何判断两个类是否相等?

  • 在 Java 中,类的唯一性 由类的全限定名(包含包名的类名)和 类加载器 共同决定。
  • 判断标准:
    • 同一类加载器: 两个类如果由相同的类加载器加载,并且来自相同的 .class 文件,则可以认为是相等的类。
    • 不同类加载器: 即使两个类来自相同的 .class 文件,但如果它们是由不同的类加载器加载的,它们也被认为是不同的类。
  • 因此,类的相等性不仅取决于类的源文件,还取决于加载它的类加载器。这意味着在同一个 JVM 中,不同的类加载器可以加载相同名字的类,它们在 JVM 中会被视为不同的类。这种特性通常用于应用服务器或模块化系统中,以便在不同的上下文中隔离类加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值