目录
[一、JVM 基础认知](#一、JVM 基础认知)
[1.1 JVM 核心概念](#1.1 JVM 核心概念)
[1.2 JDK、JRE、JVM 三者区别](#1.2 JDK、JRE、JVM 三者区别)
[1.3 JVM 运行流程](#1.3 JVM 运行流程)
[二、JVM 内存运行模型](#二、JVM 内存运行模型)
[2.1 程序计数器(线程私有)](#2.1 程序计数器(线程私有))
[2.2 Java 虚拟机栈(线程私有)](#2.2 Java 虚拟机栈(线程私有))
[2.3 本地方法栈(线程私有)](#2.3 本地方法栈(线程私有))
[2.4 堆(线程共享)](#2.4 堆(线程共享))
[2.5 元数据区(方法区,线程共享)](#2.5 元数据区(方法区,线程共享))
[2.6 常见内容溢出问题](#2.6 常见内容溢出问题)
[三、JVM 类加载机制](#三、JVM 类加载机制)
[3.1 类加载过程](#3.1 类加载过程)
[3.2 类加载器与双亲委派模型](#3.2 类加载器与双亲委派模型)
一、JVM 基础认知
1.1 JVM 核心概念
**JVM(Java虚拟机):**运行 Java 字节码的虚拟计算机,是实现 Java 一次编写,到处运行的核心。它屏蔽了底层操作系统、硬件差异,Java程序并非直接运行在系统上,而是依托 JVM 执行。
虚拟机分为两大类:系统虚拟机、程序虚拟机
- **系统虚拟机(如 VMware):**完整模拟一套硬件电脑,可独立安装操作系统
- **程序虚拟机(如 JVM):**只执行特定语言的字节码,不模拟完整硬件,用于跨语言、跨平台使用
1.2 JDK、JRE、JVM 三者区别
- **JDK:**Java开发工具包 = JRE+编译、调试等开发工具,用于开发+运行
- **JRE:**Java运行时环境 = JVM+核心类库,仅能运行Java程序
- **JVM:**虚拟机,只负责运行字节码,是最底层运行容器
它们三者关系如下图所示:
1.3 JVM 运行流程
程序在执行前先要把 .java 源代码编译成字节码(.class字节码文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader)把文件加载到运行时数据区(内存),
由于字节码文件是JVM的一套指令集规范,并不能直接交给底层操作系统执行,因此需要特定的命令解析器(执行引擎) 将字节码翻译成底层系统指令再交给CPU执行,这个过程需调用其他语言的接口**(本地库接口)** 来实现整个程序的功能,最后垃圾回收(GC),程序运行结束,资源释放。
二、JVM 内存运行模型

2.1 程序计数器(线程私有)
**程序计数器是用来记录当前线程执行到的字节码行号。**如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行一个Native方法,这个计数器为空。
程序计数器内存区域是唯一一个在Java规范中无OOM的内存区域
2.2 Java 虚拟机栈(线程私有)
Java 虚拟机栈的生命周期与线程相同,描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
Java虚拟机栈中包含:
按照后进先出的出栈规律
- **局部变量表:**存放方法中的局部变量和方法参数,局部变量表的内存空间在编译期间就完成分配,在执行期间不会改变局部变量表的大小
- **操作数栈:**方法执行的 " 临时计算器 ",先进后出
- **动态链接:**把符号引用变成真正的内存地址,帮助找到方法到底在内存的哪个位置
- **方法返回地址:**方法执行完之后回到哪里继续执行
2.3 本地方法栈(线程私有)
本地方法栈是给C++代码使用的(也就是我们所说的本地方法native)
线程私有:
- 每个线程独占一份,线程之间互不干扰、数据隔离
- 线程创建→自动开辟对应内存;线程销毁→内存立即释放
2.4 堆(线程共享)
程序中创建的所有对象都保存在堆中(new),最大内存区域
垃圾回收主要区域,分新生代、老年代,最常发生OOM(内存溢出)
2.5 元数据区(方法区,线程共享)
用来存储类信息、常量、静态变量、即时编译器编译的代码
2.6 常见内容溢出问题
- **OOM(OutOfMemoryError):**统称:内存溢出,指 JVM 没有内存可以分配
- 栈溢出(StackOverflowError): 线程私有区域溢出,栈帧太多,方法调用太深(例如无限递归)
- **堆溢出(Heap OOM):**OOM的一种最常见形式,new的对象太多了
三、JVM 类加载机制
下面是类加载的生命周期:

3.1 类加载过程
- **加载:**将 .class 文件找到,打开文件并且读取文件的数据到内存中,根据代码中写的 " 全限定类名 "(import...),找到对应的class文件
- **验证:**检查字节码是否合法、安全
- **准备:**给创建的类对象分配内存,赋默认值
- **解析:**将常量池的符号引用替换为直接引用,也就是初始化常量
- **初始化:**针对类对象进行初始化,初始化类的静态成员,执行静态代码块,对父类的加载
类加载的时机:
- 在new这个类的实例的时候
- 在调用这个类的静态方法或访问类的静态成员
- 在对其子类进行加载时,也会触发父类的加载
类加载只加载一次(单例)
3.2 类加载器与双亲委派模型
双亲委派模型出现在类加载的第一步 (加载),因此就有了类加载器的出现
概念:双亲委派模型是指如果一个类加载器收到了类加载的请求,它首先不会去加载这个类,而是交给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都会到达最顶层的启动类加载器中,只有当父加载器无法完成这个请求时,子类加载器才会尝试去加载完成,如果都未加载成功会抛出 ClassNotFoundException异常
JVM中默认包含3个类加载器:
- **BootstrapClassLoader :**负责加载标准库中的类
- **ExtensionClassLoader:**负责加载Java扩展库中的类(JDK厂商进行的扩展)
- **ApplicationClassLoader:**负责加载第三方库,以及当前项目中的类
核心作用:
- 安全防护:防止核心类被篡改,会优先加载原生类
- 避免重复加载:同一个类只会被加载一次
- 统一类加载规则:保证全环境中基础类版本一致





