目录

Java 中 双亲委派 是什么?有啥用?

双亲委派模型的作用

讲一下类加载过程?

讲一下类的加载和双亲委派原则


Java 中 双亲委派 是什么?有啥用?

Java中类加载器从顶层开始依次是启动类加载器(Bootstrap ClassLoard加载Java的底层类如java.lang.string)、扩展类加载器(Extention ClassLoard加载JDK扩展目录类)、应用类加载器(AppClassLoard加载自己写的类和第三方jar包)、自定义类加载器在最底层

当某个类加载器有加载类的任务时,会把任务给上一层的加载器,如自定义加载器的加载类的任务会给应用加载器,最终到启动类加载器,如果最顶层的加载器完成不了再由自己加载

举个例子:我们自己写了一个java.lang.String类,当AppClassLoader要加载它时,会先委派给Extension ClassLoader,再委派给Bootstrap ClassLoader。而启动类加载器发现自己已经加载过 JDK 自带的String类了,就直接返回这个类,不会去加载我们自定义的String类。

这种机制的核心作用有两个:

保证类的唯一性和安全性:避免同一个类被不同加载器重复加载,确保核心类(如 JDK 的StringInteger)不会被篡改。比如上面的例子,防止我们自定义的String类替换掉 JDK 的核心类,否则可能引发安全问题(比如修改String的底层实现导致系统混乱)。

实现类的复用:核心类只需要被顶层加载器加载一次,所有子加载器都能共享这个类,减少内存消耗。

双亲委派模型的作用

保证类的唯一性:通过委托机制,确保了所有加载请求都会传递到启动类加载器,避免了不同类加载器重复加载相同类的情况,保证了Java核心类库的统一性,也防止了用户自定义类覆盖核心类库的可能。

保证安全性:由于Java核心库被启动类加载器加载,而启动类加载器只加载信任的类路径中的类,这样可以防止不可信的类假冒核心类,增强了系统的安全性。例如,恶意代码无法自定义一个 java.lang.System 类并加载到 JVM 中,因为这个请求会被委托给启动类加载器,而启动类加载器只会加载标准的 Java 库中的类。

如果我写了一个java.lang.System,因为委派模型所以会加载到启动类然后启动类会拒绝这个类的加载,只会加载自己信任的类的路径,这里指官方路径下的类

支持隔离和层次划分:双亲委派模型支持不同层次的类加载器服务于不同的类加载需求,如应用程序类加载器加载用户代码,扩展类加载器加载扩展框架,启动类加载器加载核心库。这种层次化的划分有助于实现沙箱安全机制,保证了各个层级类加载器的职责清晰,也便于维护和扩展。

简化了加载流程:通过委派,大部分类能够被正确的类加载器加载,减少了每个加载器需要处理的类的数量,简化了类的加载过程,提高了加载效率。

讲一下类加载过程?

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括以下 7 个阶段:

类加载过程

加载:通过类的全限定名(包名 + 类名),获取到该类的.class文件的二进制字节流,将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构,在内存中生成一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口

注意:Class 对象不是业务实例对象,是描述这个类本身模板信息的入口,封装了类的结构、方法、字段、常量池等元数据。

连接:验证、准备、解析三个阶段统称为连接

        验证:保证加载的类不会危害到JVM虚拟机的安全,分为4个阶段:

                文件格式校验、元数据校验、字节码校验、符号引用校验

        准备:为类中的静态字段初始化值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了

        解析:JVM把常量池中的作为名字代称的符号引用,转换为直接引用,符号引用仅以字面量描述目标、与内存布局无关,而直接引用是指针、偏移量等真实内存定位信息,解析完成即代表引用目标已载入内存,可以直接访问。

解析 = 把代码里 “名字形式的符号引用”,翻译成内存里 “能直接找到地址的直接引用”。

初始化:执行类的构造方法,clinit()

注意的是这里的 <clinit>() 并不是开发者写的构造函数(那个是实例构造器 <init>()),而是编译器自动收集类中所有静态变量的赋值语句和静态代码块合并生成的

使用:使用类或者创建对象

卸载:需要满足三个条件

1.类的实例(new user)已经全部收回

2.与类关联的ClassLoard已经收回

3.类对应的java.lang.class对象没有被任何地方引用,如User.class还存在代码中

讲一下类的加载和双亲委派原则

我们把 Java 的类加载过程分为三个主要步骤:加载、链接、初始化

首先是加载阶段(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,如 jar 文件、class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。

加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。

第二阶段是链接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程中。这里可进一步细分为三个步骤:

  • 验证(Verification),这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规的信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。
  • 准备(Preparation),创建类或接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显式初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。
  • 解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。

最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

再来谈谈双亲委派模型,简单说就是当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐