JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程

文章目录

一、类加载器

  • 1.1 什么是类加载器、类加载器作用

  • 1.2 应用场景

  • 1.3 类加载时机

  • 1.4 类加载器分类

    • 1.4.1 概述
    • 1.4.2 JDK8及之前的版本
    • 1.4.3 JDK9之后的类加载器

二、双亲委派模型

  • 2.1 什么是双亲委派模型
  • 2.2 JVM为什么采用双亲委派机制
  • 2.3 打破双亲委派机制
  • 2.4 总结

三、说一下类装载的执行过程

  • 3.1 加载
  • 3.2 验证
  • 3.3 准备
  • 3.4 解析
  • 3.5 初始化
  • 3.6 使用
  • 3.7 小节

四、小节

本文重点介绍JVM面试题——类加载器、双亲委派模型、类装载的执行过程。对于相关面试题,进行重点归纳总结;如果想查看具体详情,可参考 类加载器 超详解:什么是类加载器,类加载器作用及应用场景,类加载时机,类加载的完整过程,类加载器分类JVM —— 类加载器的分类,双亲委派机制

一、类加载器

1-1.png

1.1 什么是类加载器、类加载器作用

  • 类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
  • 类加载器会通过二进制流的方式获取到字节码文件的内容,接下来将获取到的数据交给Java虚拟机,虚拟机会在方法区和堆上生成对应的对象保存字节码信息(类加载器只参与加载过程中的字节码获取并加载到内存这一部分)
  • JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来。

1-2.png

作用:负责将.class文件(存储的物理文件)加载在到内存中。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据

1-3.png

1.2 应用场景

1-4.png

  • 企业级应用
    • SPI机制
    • 类的热部署
    • Tomcat类的隔离
  • 大量的面试题
    • 什么是类的双亲委派机制
    • 打破类的双亲委派机制
    • 自定义类加载器
  • 解决线上问题
    • 使用Arthas不停机解决线上故障

1.3 类加载时机

简单理解:字节码文件什么时候会被加载到内存中?

有以下几种情况:

  • 创建类的实例(对象)
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类

总结而言:用到了就加载,不用不加载

1.4 类加载器分类

1.4.1 概述

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。

  • 虚拟机底层实现:源代码位于Java虚拟机的源码中,实现语言与虚拟机底层语言一致,比如Hotspot使用C++。主要目的是保证Java程序运行的基础类被正确地加载,比如java.lang.String,Java虚拟机需要确保其可靠性。

  • JDK中默认提供或者自定义(重点关注):JDK中默认提供了多种处理不同渠道的类加载器,程序员也可以自己根据需求使用Java语言定制。所有Java中实现的类加载器都需要继承ClassLoader这个抽象类。

1-5.png

类加载器的设计,JDK8和8之后的版本差别较大(JDK9之后,出现了模块化设计)。

1.4.2 JDK8及之前的版本

首先来看JDK8及之前的版本,JDK8及之前的版本中默认的类加载器有如下几种

  • 启动类加载器(Bootstrap ClassLoader、C++实现):加载JAVA_HOME/jre/lib目录下的库,加载核心类,String类。它是JVM的一部分,负责加载Java核心类库,如java.lang包中的类。它是最顶层的类加载器,通常使用C++实现,无法在Java代码中直接获取到。通常表示为null ,并且没有父null(通用且重要)

  • 扩展类加载器(Extension ClassLoader、Java实现):主要加载JAVA_HOME/jre/lib/ext目录中的类。加载扩展类,拓展Java中比较通用的类,只是通用,不是特别重要,最重要的在启动类加载器加载了。通常位于JRE的lib/ext目录下

  • 应用程序类加载器(Application ClassLoader、Java实现):也称为系统类加载器(System ClassLoader),加载classPath下的类。加载应用classpath中的类,包括我们自己写的类,还有第三方Jar包的类

  • 自定义类加载器(Java实现):可以通过继承 java.lang.ClassLoader 类来自定义类加载器,需要重写findClass方法,实现自定义类加载规则。自定义类加载器可以灵活加载类,实现各种特定需求,比如从网络下载类文件、解密等。

    JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader)

1-6.png

1-7.png

各自类加载器的详细介绍 可参考 类加载器 超详解:什么是类加载器,类加载器作用及应用场景,类加载时机,类加载的完整过程,类加载器分类

1.4.3 JDK9之后的类加载器

1-8.png

由于JDK9引入了module的概念,类加载器在设计上发生了很多变化:

1)启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。

启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一

1-9.png

2)扩展类加载器被替换成了平台类加载器(Platform Class Loader)。平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinCLassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑

1-10.png

二、双亲委派模型

2.1 什么是双亲委派模型

双亲委派机制(Parent Delegation Model)是Java类加载器的一种工作方式,用于保证类的加载安全性一致性

加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类

具体介绍:如果一个类加载器收到了类加载请求、需要加载某个类时,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

2-1.png

2-2.png

2-3.png

2-4.png

2-5.png

双亲委派机制-问题

  • 重复的类:如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载? ——启动类加载器加载,根据双亲委派机制,它的优先级是最高的
  • String类能覆盖吗:在自己的项目中去创建一个java.lang.String类,会被加载吗? ——不能,会返回启动类加载器加载在rt.jar包中的String类

双亲委派具体细节 可参考 JVM —— 类加载器的分类,双亲委派机制

2.2 JVM为什么采用双亲委派机制

(1)通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。

(2)为了安全,保证类库API不会被修改

package java.lang;
public class String {
   
    public static void main(String[] args) {
   
        System.out.println("demo info");
    }
}

由于是双亲委派的机制,java.lang.String的在启动类加载器得到加载,因为在核心jre库中有其相同名字的类文件,但该类中并没有main方法。这样就能防止恶意篡改核心API库。

此时执行main函数,会出现异常,在类 java.lang.String 中找不到 main 方法

2-6.png

总的来说,双亲委派机制可以保证类的一致性、安全性和隔离性,避免重复加载,同时也提供了灵活的扩展性,使得类加载器可以根据特定需求进行定制。

而虽然双亲委派机制为JAVA类的加载提供了很好的安全性和便捷性。但是有的时候我们不得不打破双亲委派机制,例如:一个Tomcat容器中可以运行多个WEB应用,而如果这两个应用中出现了同名的A类,那么Tomcat就要保证这两个A类都被加载并且是各自不同的类。如果不打破双亲委派机制,那么WEB1中的A类记载后,WEB2中自己的A类就不会加载成功了,按照双亲委派机制来讲,此时会直接返回WEB1中的A类。此时我们就需要打破双亲委派机制。

2.3 打破双亲委派机制

打破双亲委派机制的三种方式:

  • 自定义类加载器
    • 自定义类加载器并且重写loadClass方法,就可以将双亲委派机制的代码去除
    • Tomcat通过这种方式实现应用之间类隔离
  • 线程上下文加载器
    • 利用上下文类加载器加载类,比如JDBC和JNDI等。SPI机制+线程上下文类加载器
  • Osgi框架的类加载器(了解即可)
    • 历史上Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载

2-7.png

双亲委派具体细节 可参考 JVM —— 类加载器的分类,双亲委派机制

2.4 总结

(1)什么是双亲委派模型

加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类

  • 当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载(自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性
  • 应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。
  • 双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

(2)JVM为什么采用双亲委派机制

2-8.png

  • 通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
  • 为了安全,保证类库API不会被修改

三、说一下类装载的执行过程

类从加载到虚拟机中开始、直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)

3-1.png

3.1 加载

  • 通过类的全名(包名 + 类名),获取类的二进制数据流
  • 将这个类加载到内存中:解析类的二进制数据流为方法区内的数据结构(Java类模型)。
  • 加载完毕创建一个class对象,即创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口

3-2.png

3-3.png

3.2 验证

确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全(文件中的信息是否符合虚拟机规范有没有安全隐患)。

3-4.png

3-5.png

3.3 准备

负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值(初始化静态变量)

  • static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成
  • static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成
  • static变量是final的引用类型,那么赋值也会在初始化阶段完成

3-6.png

public class Application {
   
    static int b = 10;
    static final int c = 20;
    static final String d = "hello";
    static final Object obj = new Object();
}

3-7.png

3.4 解析

将类的二进制数据流中的符号引用替换为直接引用(本类中如果用到了其他类,此时就需要找到对应的类)

3-8.png

比如方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。

3-9.png

3-10.png

3.5 初始化

根据程序员通过程序制定的主观计划去初始化类变量和其他资源(静态变量赋值以及初始化其他资源)

  • 对类的静态变量、静态代码块执行初始化操作
  • 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类
  • 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行

3-11.png

3-12.png

3.6 使用

JVM开始从入口方法执行用户的程序代码

  • 调用静态类成员信息(比如:静态字段、静态方法)
  • 使用new关键字为其创建对象实例

3-13.png

3.7 小节

1)当一个类被使用的时候,才会加载到内存

2)类加载的执行过程: 加载、验证、准备、解析、初始化、使用、卸载

  • 加载:查找和导入class文件
  • 验证:保证加载类的准确性
  • 准备:为类变量分配内存并设置类变量初始值
  • 解析:把类中的符号引用转换为直接引用
  • 初始化:对类的静态变量,静态代码块执行初始化操作
  • 使用:JVM 开始从入口方法开始执行用户的程序代码
  • 卸载:当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象。

四、小节

(1)什么是类加载器

JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来。

(2)类加载器的作用是什么

类加载器(ClassLoader)负责在类加载器过程中的字节码获取并加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据

(3)类加载器有哪些/有几种常见的类加载器

  • 启动类加载器(BootStrap ClassLoader):加载JAVA_HOME/jre/lib目录下的库,加载核心类

  • 扩展类加载器(Extension ClassLoader):主要加载JAVA_HOME/jre/lib/ext目录中的类,加载扩展类

  • 应用类加载器(Application ClassLoader):用于加载classPath下的类

  • 自定义类加载器(Customize ClassLoader):自定义类继承ClassLoader,重写findClass方法,实现自定义类加载规则。

    JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(Platform ClassLoader)

4-1.png

(4)什么是双亲委派机制

每个Java实现的类加载器中保留了一个成员变量叫“父”(Parent)类加载器。

  • 当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载(自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性
    • 加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类
  • 应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。
  • 双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

4-2.png

(5)JVM为什么采用双亲委派机制

4-3.png

  • 通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
  • 为了安全,保证类库API不会被修改

(6)怎么打破双亲委派机制

  • 重写loadClass方法,不再实现双亲委派机制
  • JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器
  • OSGi实现了一整套类加载机制,允许同级类加载器之间互相调用

(7)说一下类装载的执行过程

4-4.png

  • 加载:查找和导入class文件
  • 验证:保证加载类的准确性
  • 准备:为类变量分配内存并设置类变量初始值
  • 解析:把类中的符号引用转换为直接引用
  • 初始化:对类的静态变量,静态代码块执行初始化操作
  • 使用:JVM 开始从入口方法开始执行用户的程序代码
  • 卸载:当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象。

参考 黑马程序员相关视频及笔记

相关文章
|
2月前
|
安全 前端开发 Java
【JVM的秘密揭秘】深入理解类加载器与双亲委派机制的奥秘!
【8月更文挑战第25天】在Java技术栈中,深入理解JVM类加载机制及其双亲委派模型是至关重要的。JVM类加载器作为运行时系统的关键组件,负责将字节码文件加载至内存并转换为可执行的数据结构。其采用层级结构,包括引导、扩展、应用及用户自定义类加载器,通过双亲委派机制协同工作,确保Java核心库的安全性与稳定性。本文通过解析类加载器的分类、双亲委派机制原理及示例代码,帮助读者全面掌握这一核心概念,为开发更安全高效的Java应用程序奠定基础。
83 0
|
2天前
|
Java
【JVM】双亲委派模型
【JVM】双亲委派模型
12 1
|
3天前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
24天前
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
2月前
|
消息中间件 NoSQL 领域建模
这些年背过的面试题——领域模型落地篇
本文是技术人面试系列领域模型落地篇,也是面试题系列的完结篇,感谢大家对本系列文章的支持~面试中关于领域模型落地都需要了解哪些内容?一文带你详细了解,欢迎收藏!
|
2月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
2月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
41 0
|
2月前
|
开发者 C# Windows
WPF布局大揭秘:掌握布局技巧,轻松创建响应式用户界面,让你的应用程序更上一层楼!
【8月更文挑战第31天】在现代软件开发中,响应式用户界面至关重要。WPF(Windows Presentation Foundation)作为.NET框架的一部分,提供了丰富的布局控件和机制,便于创建可自动调整的UI。本文介绍WPF布局的基础概念与实现方法,包括`StackPanel`、`DockPanel`、`Grid`等控件的使用,并通过示例代码展示如何构建响应式布局。了解这些技巧有助于开发者优化用户体验,适应不同设备和屏幕尺寸。
32 0
|
2月前
|
机器学习/深度学习 算法 数据挖掘
|
2月前
|
安全 前端开发 Java
【JVM 探秘】ClassLoader 类加载器:揭秘 Java 类加载机制背后的秘密武器!
【8月更文挑战第25天】本文全面介绍了Java虚拟机(JVM)中的类加载器,它是JVM的核心组件之一,负责将Java类加载到运行环境中。文章首先概述了类加载器的基本工作原理及其遵循的双亲委派模型,确保了核心类库的安全与稳定。接着详细阐述了启动、扩展和应用三种主要类加载器的层次结构。并通过一个自定义类加载器的例子展示了如何从特定目录加载类。此外,还介绍了类加载器的完整生命周期,包括加载、链接和初始化三个阶段。最后强调了类加载器在版本隔离、安全性和灵活性方面的重要作用。深入理解类加载器对于掌握JVM内部机制至关重要。
75 0