目录
[JVM 内存划分](#JVM 内存划分)
[JVM 类加载机制](#JVM 类加载机制)
[类被加载的几种情况(懒汉模式 ---> 只要被用到才会被加载)](#类被加载的几种情况(懒汉模式 ---> 只要被用到才会被加载))
JVM 内存划分
- JVM 是一个应用程序,在它启动的时,需要从操作系统中申请内存一部分内存区域!
- 每个 JVM 就是一个 Java 进程
划分图:
本地方法栈
- 本地则表示 JVM 内部的 C++ 代码,就是给调用本地方法(JVM 内部的方法)所准备的栈空间
虚拟机栈
- 给 Java 代码使用的栈空间,用来存储局部变量、方法参数、返回值 等
注意:
- 此处的栈跟数据结构中的栈不是同一个东西
- 数据结构的栈是一个通用的,更广泛的概念,此处的栈特指 JVM 中的一个特定空间
- 本地方法栈 存储本地方法之间的调用关系
- 虚拟机栈 存储方法之间的调用关系
- 整个栈空间包含多个栈帧,每个栈帧表示一个方法,其中包含方法的入口地址、参数、返回地址、局部变量等
- 每个线程都有自己独立的栈
程序计数器
- 存储当前线程执行的字节码指令地址,记录当前线程执行到哪个指令,每个线程都有自己独立的程序计数器
堆区
- 堆是 JVM 中最大的一块内存区域,用于存储对象实例和数组,一个进程只有一份,被所有线程共享,通过垃圾回收来管理内存的分配与释放
元数据区
- 用来存储类的信息、常量、静态变量、静态成员 等
总结:
- 局部变量在栈上
- 普通成员变量在堆上
- 静态成员变量在元数据区上
JVM 类加载机制
- 指将类的字节码(.class文件)加载到内存中(元数据区),并在运行时将其转换为可执行的 Java 类
机制图:
加载
- 通过类加载器,根据类的名字查找相对应的字节码文件,把类的字节码数据(.class文件)加载到 JVM 中并转化为其内部的数据结构
验证
- 验证阶段 JVM 检查字节码的格式、语义、安全性等方面的问题,以确保被加载的字节码符合 JVM 规范的过程
准备
- 准备阶段 JVM 为类的静态变量(static修饰的变量)分配内存空间,并根据其类型设置如 0 、false、null 这样的默认初始值
解析
- 解析阶段是将常量池中的 符号引用(类、方法、字段等符号) 替换为 直接引用(指向实际内存地址的指针) 的过程,也就是初始化常量的过程
初始化
- 初始化阶段是执行类的初始化代码的过程,JVM 会按照代码顺序执行类的静态初始化块和静态变量的赋值操作
类被加载的几种情况(懒汉模式 ---> 只要被用到才会被加载)
- 构造类的实例
- 调用这个类的静态方法、使用这个类的静态属性
- 加载子类,便会加载其父类
- 该类一旦被加载过,后续再使用便不会重复加载
双亲委派模型
- 属于 Java 类加载机制中的一种设计模式,描述的是加载阶段,寻找 .class 文件的基本过程
- 默认提供了三个类加载器
|----------------------------|----------------------------|------------------------------|
| Bootdtrsp Class Loader | Extension Class Loader | Application Class Loader |
| 加载标准库中的类 | 加载 JVM 拓展库中的类 | 加载用户提供的第三方库、用户项目代码 中的类 |
- 上述三个类加载器存在父子关系 ,子 类加载器上有一个 parent 属性指向其父 类加载器
具体类加载过程:
当一个类加载器收到加载类的请求时,它首先检查是否已经加载过这个类。如果已经加载过,则直接返回该类的 Class 对象
如果类没有被加载过,那么该类加载器会将加载任务委派给父加载器去完成
父加载器会按照相同的规则进行加载,即首先检查是否已经加载过该类,如果加载过则返回 Class对象,否则继续委派给它的父加载器
这个过程一直往上委派,直到达到顶层的启动类加载器(Bootdtrsp Class Loader)。启动类加载器是 JVM 的一部分,它负责加载 Java 的标准库
如果顶层的启动类加载器无法加载类,那么其子加载器会尝试自己加载类,它会通过自己的类路径搜索字节码文件,并将字节码转换为 Class 对象
例图:(加载 java.lang 类)
- 这个加载顺序的主要目的是为了让 Bootdtrsp Class Loader 启动类能够先加载,Application Class Loader 启动类后加载。因为这样可以避免用户创建一些奇怪的类,引起不必要的 bug(用户自己写的类与标准库中的类重名)
双亲委派模型的优点:
避免重复加载:每个类加载器都会缓存已加载的类,避免了同一个类被多次加载的情况
安全性:通过双亲委派模型,核心类库由启动类加载器加载,这样可以保证核心类库的安全性,防止恶意代码替换核心类
一致性:相同的类在不同的类加载器中只会存在一个实例,保证了类的一致性
可扩展性:开发者可以通过自定义类加载器并继承现有的加载器,实现特定的类加载行为,从而实现动态的类加载和扩展