【JVM】类加载机制及双亲委派模型

2024-05-08 05:12

本文主要是介绍【JVM】类加载机制及双亲委派模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、类加载过程

1. 加载

2. 连接

a. 验证

b. 准备

c. 解析

3. 初始化

二、双亲委派模型 

类加载器 

双亲委派模型的工作过程

双亲委派模型的优点


一、类加载过程

JVM的类加载机制是JVM在运行时,将 .class 文件加载到内存中并转换为Java类的过程。它是Java语言实现跨平台特性的核心之一。

对于一个类来说,它的生命周期是这样的:

其中,类加载的过程主要可以分成 5 个步骤(前 5 步),中间 3 步都属于连接,所以也可以说类加载过程主要分成 3 个步骤:

1. 加载

2. 连接

  • 验证
  • 准备
  • 解析

3. 初始化

下面我们来看每个步骤的具体执行内容:

1. 加载

“加载”阶段是整个“类加载”过程中的一个阶段,它和类加载是不同的。

加载阶段是指将类的字节码文件加载到内存中的过程。在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。(全限定名,例如:java.lang.String)

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3)在内存中生成一个代表这个类的 .class 对象,作为方法区这个类的各种数据的访问入口。

2. 连接

a. 验证

确保加载的类符合JVM规范和Java语言规范,即确保读到的 .class 文件(字节码文件),是合法的格式。

文件格式如下图所示,此处就不详细介绍了。

b. 准备

给类的静态变量分配内存空间,并设置类变量的默认初始值。

比如此时有这样一行代码:

public static int value = 123;

它是初始化 value 的值为0(默认值),而非 123。

c. 解析

解析阶段是 JVM 将类、接口、字段和方法(运行时常量池)的符号引用解析为直接引用。

  • 符号引用的获取: 在加载阶段和连接阶段之前,Java虚拟机会将类、接口、字段和方法的符号引用存储在运行时常量池中。解析阶段首先要做的就是从运行时常量池中获取符号引用。

  • 符号引用的匹配: 获取到符号引用后,解析阶段会尝试将这些符号引用匹配到目标对象(类、接口、字段或方法)的直接引用。匹配的过程包括查找目标对象在内存中的位置以及确定访问权限等。

  • 直接引用的生成: 一旦符号引用成功匹配到目标对象,解析阶段就会生成对应的直接引用。直接引用是指直接指向内存中目标对象的指针或偏移量,它能够直接在程序中被使用。

  • 解析结果的存储: 解析阶段最终会将生成的直接引用存储在运行时常量池中,以便后续的使用。

3. 初始化

初始化阶段是类加载过程的最后一个阶段,主要负责执行类变量的赋值操作和静态代码块的初始化。

  • 执行类变量的赋值操作,即按照程序员在代码中指定的初始值为静态变量赋值。这些值可以是程序中直接赋予的值,也可以是静态代码块中的计算结果。
  • 如果类中存在静态代码块,则会按照在代码中的顺序执行静态代码块中的内容。静态代码块中可以包含任意合法的 Java 代码,用于执行一些静态初始化操作。

上述的一系列类加载过程,可以简单概况为以下内容:

  1. 加载(Loading): 加载阶段是指将类的字节码文件加载到内存中的过程。当程序中使用到某个类时,JVM会通过类的全限定名(Fully Qualified Name)来加载类。类加载器会根据类的全限定名在文件系统或网络中查找相应的.class文件,并将其加载到内存中。

  2. 连接(Linking): 连接阶段包括验证、准备和解析三个步骤:

    • 验证(Verification): 确保加载的类符合JVM规范和Java语言规范,以防止恶意代码的注入。
    • 准备(Preparation): 为类的静态变量分配内存空间,并设置默认初始值。
    • 解析(Resolution): 将类、接口、字段和方法的符号引用解析为直接引用,以便后续执行。
  3. 初始化(Initialization): 初始化阶段是类加载过程的最后一个阶段,它负责执行类构造器的<clinit>方法,即对类的静态变量进行赋值操作和执行静态代码块。

二、双亲委派模型 

提到类加载机制,就不得不提“双亲委派模型”。它是 Java 类加载机制中的一种设计思想,JVM的类加载机制采用的就是双亲委派模型。

类加载器 

在 JVM 中,有一个重要的组件称为“类加载器”,它负责加载 Java 类文件到 JVM 中(根据类的全限定名来查找并加载对应的类文件,例如:java.lang.String)。JVM 中的类加载器默认有以下三种:

  1. 启动类加载器(Bootstrap Class Loader): 它是 JVM 的内置类加载器,负责加载 Java 核心类库(rt.jar)等核心类文件(标准库),是整个类加载器层次结构的顶层。由于它是用本地代码实现的,所以在 Java 中无法直接获取对其的引用。

  2. 扩展类加载器(Extension Class Loader): 它是用来加载 Java 平台的扩展库(ext 目录下的jar包)的类加载器。它的父类加载器是启动类加载器。通常情况下,我们可以通过 ClassLoader.getSystemClassLoader().getParent() 获取到扩展类加载器的引用。

  3. 应用程序类加载器(Application Class Loader): 也称为系统类加载器,它是用来加载应用程序的类文件的类加载器,它负责加载类路径(classpath)上指定的类库。它的父类加载器是扩展类加载器。通常情况下,我们可以通过 ClassLoader.getSystemClassLoader() 获取到应用程序类加载器的引用。 

除了上述的三种主要的类加载器之外,JVM 还支持用户自定义的类加载器,用户可以根据需要实现自己的类加载器来加载特定的类文件。用户自定义的类加载器通常继承自  java.lang.ClassLoader 类,并重写其 findClass() 方法来实现类加载的逻辑。

双亲委派模型的工作过程

双亲委派模型的工作过程如下: 

  • 当一个类加载器收到类加载请求时,它不会立即尝试自己去加载这个类,而是先将这个请求委派给它的父类加载器去完成。这个过程会一直向上进行,直到达到最顶层的启动类加载器。只有当父类加载器反馈自己无法完成这个加载请求(即在其搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载这个类。

双亲委派模型的优点

上述设定有以下几个优点:

  1. 安全性: 双亲委派模型可以帮助保证 Java 类库的安全性。由于类加载器会先委托给父类加载器加载类,这样可以防止恶意类被加载到 JVM 中。父类加载器通常是由 JVM 提供的,是由Java官方实现,因此可以信任。这有助于防止在 Java 应用程序中意外加载不安全或有潜在安全风险的类。例如,我们在代码中自己定义了一个 java.lang.String 这样的类,根据双亲委派模型的设定,这个类会被启动类加载器找到并加载,此时加载的是Java核心类,而自定义的 java.lang.String 类实际上是不会被加载的,这就保证了Java核心类库中的类无法被替换。

  2. 避免重复加载类: 双亲委派模型可以避免同一个类被多次加载到 JVM 中。当一个类被加载后,它会被缓存起来,以避免重复加载。这有助于节省内存空间,并且可以确保所有代码都是基于相同的类实例运行。

  3. 统一性: 双亲委派模型可以确保 Java 类库的一致性。因为所有的类加载请求都会经过父类加载器,所以无论是在 Java 应用程序中还是在 Java 核心类库中,都可以保证加载的是同一个类。

双亲委派模型在生活中的类比:

  • 假设你在一家公司工作,你的经理接到了一个任务,他会根据任务的性质和自己的能力来判断是否能够完成这个任务。
  • 如果经理认为自己无法完成任务,他会将任务转交给更高级别的领导,如部门主管或总经理。
  • 只有当更高级别的领导无法完成任务时,任务才会逐级向下转交,直到有可能被转交给你。这种机制确保了任务能够被最适合的人完成,提高了工作效率和质量。

双亲委派模型,是Java虚拟机(JVM)遵循的默认类加载机制。但也有一些方式能够打破这个机制,这里简单介绍:

  • 自定义类加载器: 开发自定义类加载器可以完全改变类加载的方式。通过实现自定义的ClassLoader类,可以实现不同于双亲委派模型的加载行为。例如,可以实现一个不遵循双亲委派模型的类加载器,直接从指定的位置加载类,而不是按照双亲委派模型从上至下逐级加载。

这篇关于【JVM】类加载机制及双亲委派模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/969399

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis