在Java程序编译成.class文件中,想在JVM中运行,需将.class加载到JVM中的运行时数据区(方法区)中,这是JVM的类加载子系统就发挥了自己的作用;
【问题:如何判断;两个对象是否相等?】
【回答】首先要判断对象所属类的类加载器是否是一样,接下来再比较两个对象的地址是否相等;
一、 JVM内存结构
按照JVM的结构大致将JVM分为三层,其中类加载子系统属于最上面一层,是Java程序执行的开始;
- 类加载器子系统
- 运行时数据区
- 执行引擎
这三层详细的结构图如下:
二、 类加载器与类的加载过程
1. 类加载器
类加载的结构图如下:
类加载的作用:
- 负责从文件系统或网络中加载.Class文件,class文件的头部有特殊的文件标识(CAFE BABE);
- 加载的类型存储在一块成为方法区的内存空间中;
- .class文件 ----> JVM -----> 元数据模板,Class Loader,扮演快递员的角色;
2. 类的加载过程
类的加载过程包含加载、链接(验证、准备、解析)、初始化三个阶段,如下图所示:
2.1 加载(Loading)
在加载阶段,需要完成以下三件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流;
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
注意:加载阶段和链接阶段的部分动作是交叉执行的,并不是一定的先后顺序;
补充:加载.class文件的方式:
- 从本地系统中直接加载
- 通过网络获取,典型场景:web Applet
- 从zip压缩包中读取,成为日后jar、war格式的基础
- 运行时计算生成,使用最多的是:动态代理技术
- 有其它文件生成,典型场景:JSP应用
- 从专用数据库中提取.class文件,比较少见
- 从加密文件中获取,典型的防class文件被反编译的保护措施;
数组类加载的不同:
数组类的两个概念:
- 数组类的元素类型:Element Type,指的是数组去掉所有维度的类型;
- 数组类的组件类型:Component Type,指的是数组去掉一个维度的类型;
数组类加载的不同点:
- 数组类本身不通过类加载器加载,由Java虚拟机直接在内存中动态构造出来,但数组的元素类型最终还是要靠类的加载器来完成加载;
- 如果组件类型是引用类型,则使用上述定义的加载流程加载这个组件类型,数组会被标识在加载该组件类型的类加载器的类名称空间上;
- 如果组件类型不是引用类型,则将数组标记为与引导类加载器关联;
- 数组类的可访问性与组件类型的可访问性一致,若组件类似不是引用类型,则默认为public;
2.2 链接(Linking)
链接的整体流程如下所示
基本数据零值表如下
准备阶段需要注意的点:
两个例子说明准备阶段的赋值:
【例1】
public static int value = 123;
上述类表变量在准备阶段,value赋值为0,在初始化阶段才赋值为123;
【例2】
public static final value = 123;
上述类的变量被final修饰,value在准备阶段应该赋值为123;
2.3 初始化
- 初始化阶段是执行**类构造器方法
<clinit>()
**的过程 <cliint>()
无需自定义,有javac编译器自动收集类中的所有类变量的赋值动作和静态代码块的语句合并而来- 构造器方法中的指令按语句在源文件中出现的顺序执行
<clinit>()
不同于类的构造器- 若该类有父类,在子类
<clinit>()
执行前保证父类的<clinit>()
已经执行完毕 - 虚拟机必须保证一个类的
<clinit>()
方法在多线程下被同步加锁
三、 类加载器的分类
JVM支持两种类的加载器:
- 引导类加载器(Bootstrap ClassLoader):C++实现,是虚拟机自身的一部分;
- 自定义类加载器(User-Defined ClassLoader):由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkMVGKzx-1624932803401)(E:\my_note\JVM\imgs\15.png)]
1. 引导类加载器(启动类加载器)
- 使用C/C++语言实现,嵌套在JVM内部;
- 用来加载Java的核心库,用于提供JVM自身需要的类;
- 并不继承自java.lang.ClassLoader,没有父加载器;
- 加载扩展类和程序类加载器,并指定为它们的父类;
- 只加载包名为java、javax、sun等开头的类;
2. 扩展类加载器(Extension ClassLoader)
- Java语言编写,由
sun.misc.Launcher$ExtClassLoader
实现 - 派生自ClassLoader类
- 父类加载器为启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/bin/ext子目录下加载类库;
3. 应用程序类加载器(AppClassLoader
- Java语言编写
- 派生自ClassLoader类
- 父类为扩展类加载器
- 负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
- 类加载器是程序中默认的类加载器,一般的Java应用类都是由它来完成加载
- 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器
四、双亲委派机制
**双亲委派模型的工作过程是:**如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
**源码执行流程:**先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载。
作用:
- 保证代码中所有调用核心包的类保持一致:例如String类、 Object类;
- 保证编译的一致性
JDK9之前的类加载器的三层模型和双亲委派机制
JDK9之后的类加载器三层模型和双亲委派机制