Java 类加载过程和双亲委派模型

2024-05-27 12:44

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

Java 类加载过程概述

在 Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备、解析
Java类加载过程分为如下步骤:
1.装载( 加载):查找和导入类或接口的二进制数据;通过类的全限定名获取类的二进制字节流并存储在内存中。
2.链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验(Verification):检查导入类或接口的二进制数据的正确性;验证字节流的正确性,包括文件格式、语法正确性等。
准备:给类的静态变量分配并初始化存储空间;为类的静态变量分配内存空间,并初始化为默认值。
解析:将符号引用转成直接引用;将符号引用解析为直接引用,例如将类或接口的全限定名解析为内存地址
初始化(Initialization):激活类的静态变量,初始化 Java 代码和静态 Java 代码块,执行类的初始化代码,包括静态变量的赋值和静态代码块的执行等
上述过程中,加载、验证、准备、解析是类加载的前期准备工作,而初始化阶段是类加载的最后一步,也是类真正被使用前的准备工作。

Java的类加载过程详解

Java的类加载过程是一个重要的概念,它涉及到JVM(Java虚拟机)如何动态地加载Java类到内存中,以便执行其中的代码。这个过程大致可以分为三个阶段:加载(Loading)、链接(Linking)和初始化(Initialization)。

  1. 加载(Loading)
    加载是类加载过程的第一个阶段。在这个阶段,JVM主要完成以下三件事情:
    通过一个类的全限定名来获取定义此类的二进制字节流:这个二进制字节流可以从文件系统、网络或其他来源获取。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构:在加载阶段,JVM会在方法区中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
    在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
  2. 链接(Linking)
    链接阶段又可以细分为三个阶段:验证(Verification)、准备(Preparation)和解析(Resolution)。
    2.1 验证(Verification)
    验证是确保被加载的类的正确性和安全性的阶段。主要包括以下四个方面的验证:
    文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的JVM处理。
    元数据验证:对类的元数据信息进行语义校验(比如这个类是否有父类,指定的父类是否存在等)。字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
    符号引用验证:确保解析动作能正确执行。
    2.2 准备(Preparation)
    准备阶段是为类的静态变量分配内存,并将其初始化为默认值。这里需要注意的是,这个阶段只是为静态变量分配了内存并设置为了默认值,而真正的赋值操作会在初始化阶段进行。
    2.3 解析(Resolution)
    解析阶段是JVM将常量池内的符号引用替换为直接引用的过程。符号引用是一个逻辑引用,它只包含了引用的名称和描述,而没有具体的内存地址。而直接引用则是一个指向具体内存地址的指针或者句柄。
  3. 初始化(Initialization)
    初始化阶段是类加载过程的最后一个阶段。在这个阶段,JVM才真正开始执行类中定义的Java代码(比如静态代码块、为静态变量赋值等)。如果在初始化阶段出现了异常,那么JVM会立即停止对该类的初始化,并且不会再次尝试对该类进行初始化。
    需要注意的是,类的初始化只会被执行一次。如果一个类已经被加载、链接并且初始化过了,那么再次请求加载这个类时,JVM会直接返回代表这个类的Class对象,而不会重新执行类的加载、链接和初始化过程。这就是所谓的“双亲委派模型”中的一部分。

委托模型机制
委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。
我们可以通过这样的代码来获取类加载器
ClassLoader loader = ClassName.class.getClassLoader(); ClassLoader ParentLoader = loader.getParent();
注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。

什么是双亲委派模型(Parent-Delegation Model)

双亲委派机制:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载
所谓双亲委派机制,或者叫父级委托模型,就是指按照类加载器的层级关系JVM 中加载类机制采用的是双亲委派模型,顾名思义,在该模型中,子类加载器收到的加载请求,不会先去处理,而是先把请求委派给父类加载器处理,当父类加载器处理不了时再返回给子类加载器加载;
因为安全。使用双亲委派模型来组织类加载器间的关系,能够使类的加载也具有层次关系,这样能够保证核心基础的 Java 类会被根加载器加载,而不会去加载用户自定义的和基础类库相同名字的类,从而保证系统的有序、安全。

  1. BootStrap ClassLoader 启 动ClassLoader 启动类加载器
  2. Extension ClassLoader 扩 展ClassLoader 扩展类加载器
  3. App ClassLoader 应 ⽤ClassLoader/系 统ClassLoader 应用程序类加载器
  4. Custom ClassLoader ⾃定义ClassLoader 自定义类加载器
    除了BootStrap ClassLoader,每个ClassLoader都有⼀个Parent作为⽗亲。
  5. 自底向上检查类是否已经加载;
  6. 自顶向下尝试加载类。

双亲委派机制:当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
在这里插入图片描述

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
Java的类加载器有三个,对应Java的三种类:

Bootstrap Loader // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
ExtClassLoader // 负责加载扩展类(就是继承类和实现类)
-AppClassLoader // 负责加载应用类(程序员自定义的类)
好处:安全,避免重复加载
Bootstrap 类加载器负责加载 rt.jar 中的 JDK 类文件,它是所有类加载器的父加载 器 。 Bootstrap 类 加 载 器 没 有 任 何 父 类 加 载 器 , 如 果 你 调 用String.class.getClassLoader() , 会 返 回 null , 任 何 基 于 此 的 代 码 会 抛 出NullPointerException 异常。Bootstrap 加载器被称为初始类加载器。
而 Extension 将加载类的请求先委托给它的父加载器,也就是 Bootstrap,如果没有成功加载的话,再从 jre/lib/ext 目录下或者 java.ext.dirs 系统属性定义的目录下加载类。Extension 加载器由sun.misc.Launcher E x t C l a s s L o a d e r 实现。第三种默认的加载器就是 A p p l i c a t i o n 类加载器了。它负责从 c l a s s p a t h 环境变量中加载某些应用相关的类, c l a s s p a t h 环境变量通常由 − c l a s s p a t h 或 − c p 命令行选项来定义,或者是 J A R 中的 M a n i f e s t 的 c l a s s p a t h 属性。 A p p l i c a t i o n 类加载器是 E x t e n s i o n 类加载器的子加载器。通 s u n . m i s c . L a u n c h e r ExtClassLoader 实现。 第三种默认的加载器就是 Application 类加载器了。它负责从 classpath 环境变量中加载某些应用相关的类,classpath 环境变量通常由-classpath 或-cp 命令行选项来定义,或者是 JAR 中的 Manifest 的 classpath 属性。Application 类加 载 器 是 Extension 类 加 载 器 的 子 加 载 器 。 通 sun.misc.Launcher ExtClassLoader实现。第三种默认的加载器就是Application类加载器了。它负责从classpath环境变量中加载某些应用相关的类,classpath环境变量通常由classpathcp命令行选项来定义,或者是JAR中的Manifestclasspath属性。Application类加载器是Extension类加载器的子加载器。通sun.misc.LauncherAppClassLoader 实现

两种方式来破坏双亲委派模型

第一种,集成ClassLoader抽象类,重写loadClass方法,在这个方法可以自定义要加载的类使用的类加载器。
第二种,使用线程上下文加载器,可以通过java.lang.Thread类的setContextClassLoader()方法来设置当前类使用的类加载器类型。

为什么需要双亲委派模型

在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名
类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法
保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码

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



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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory