JVM全称Java Virtual Machine,Java虚拟机,是一套软件模拟的虚拟计算机,隔离底层操作系统,实现Java 一次编译,到处运行。
Java程序从.java到CPU执行分为四个模块协同工作:
完整流程:
.java编译→.class字节码文件 → 类加载器加载至运行时数据区 → 执行引擎解析字节码 → 调用本地方法接口操作系统资源,CPU执行。
在真实的操作系统中,对于进程的地址空间进行了功能区域划分,JVM仿照真实的机器和真实的操作系统进行设计。
JVM从操作系统中申请到了一些内存空间,供JVM进程的代码使用。
JVM内存空间的划分就是把JVM自身申请到的内存空间,再把内存按不同功能进行划分。类比把一栋大楼分成若干层,每一层细致划分。
核心区域:

1. 程序计数器:记录当前线程执行字节码行号,执行Java方法存指令地址
这里的程序寄存器指内存上的空间,与计组中的程序寄存器不同,计组中的程序寄存器是在CPU上的
2. 元数据区:保存当前类被加载好的数据
.java文件经过javac编译变成.class文件,想要运行这个文件,就需要把字节码文件加载到内存中,也就是元数据区。
元信息就是指属性,例如类名称,访问权限,继承自哪些类或实现了哪些接口;方法名称,参数,返回值等。
Java8之前元数据区也称为”方法区“
3. 栈:保存方法调用关系
每次调用方法时都会进入方法内部,当方法调用完毕,返回调用位置,程序继续向后执行。这里的栈与数据结构中的栈逻辑类似,都具有后进先出的特性。
与操作系统中的栈联系:
栈区的两种异常:
StackOverflow:栈所占用的空间只有几十MB,如果递归函数出口逻辑错误,就有可能导致大量方法调用但是不返回,栈深度超出范围,造成栈溢出。4. 堆:保存对象实例,是JVM中最大的空间区域
假设创建一个对象实例:Test t = new Test()
如果堆上的对象不再使用就会被释放,也就是垃圾回收的工作。
元数据区和堆,整个Java进程共用一份,程序计数器和栈一个进程中可能有多份,每个线程各一份。
加载 → 连接(验证、准备、解析) → 初始化 → 使用 → 卸载
前3阶段合称类加载过程。
加载 Loading
java.lang.String)验证 Verification:解析,校验.class文件合规性,防止恶意字节码破坏虚拟机,并把.class里的内容转换为结构化的数据(也就是符合虚拟机规范的格式);
准备 Preparation:给类对象申请内存空间,此时的空间相当于”全0“的状态;
解析 Resolution:初始化字符串常量。字符串常量本身就包含在.class中,解析阶段就要把解析出的字符串常量放到内存中(元数据区的常量池);
初始化 Initialization:针对类对象进行最后的初始化。为类对象各种属性进行填充,包括静态成员变量。如果这个类有父类,也会触发父类的加载。
一个进程中一个类的加载只会触发一次,并不是Java程序一启动就会加载所有的类,类加载也是懒汉模式(懒加载)的一种体现。
触发类加载的时机:
JVM中有专门的模块负责类加载,默认提供了三种类加载器:
BootstrapClassLoaderExtensionClassLoaderAplicationClassLoaderAplicationClassLoader中的parent引用指向ExtensionClassLoader,ExtensionClassLoader中的parent引用指向BootstrapClassLoader,BootstrapClassLoader中的parent引用指向null,构成了类似链表关系。
这三个类加载器主要作用都是找.class文件,各自负责找的目录不同:
BootstrapClassLoader负责找Java标准库中的类ExtensionClassLoader负责找Java扩展库中的类。扩展可以认为JVM厂商对于Java的库做的扩充AplicationClassLoader负责找第三方库/当前项目。(当前学习过程中,通过Maven下载的类都属于第三方库)AplicationClassLoader作为入口开始,把“加载类”这样的任务委托给它的父亲ExtensionClassLoader来执行ExtensionClassLoader也不会立即干活,先把任务委托给它的父亲AplicationClassLoader来执行AplicationClassLoader也想踢足球,但是它的parent为空,只能自己进行类加载,从标准库中找是否匹配的.class文件。如果没找到就把任务还给孩子ExtensionClassLoaderExtensionClassLoader在扩展库中继续找.class文件,找到就加载,没找到就继续还给孩子 AplicationClassLoaderAplicationClassLoader继续负责找.class文件,找到就加载;没找到就抛出ClassNotFount异常也可以自定义类加载器,参与到双亲委派模型中
双亲委派模型两大优势
java.lang.Object篡改核心API。GC仅关注堆、方法区,虚拟机栈、本地方法栈、程序计数器生命周期随线程自动释放,无需回收。
GC主要做两个工作:
每个对象在创建的的时候,就会分配一小块内存空间,保存一个整数,表示有多少个引用指向它,每次进行引用赋值的时候,都会触发引用计数的修改。
在Java中,使用对象一定要通过引用实现,当引用计数为0时,说明没有引用指向这个对象了,这样就可以判断这个对象就是垃圾
缺陷:
举个例子:
class Test{
Test t = null;
}
class Main{
public static void main(String[] args){
Test a = new Test();//计数器+1
Test b = new Test();//计数器+1
a.t = b;//计数器+1
b.t = a;//计数器+1
}
}
此时两个对象实例各有两个引用指向

如果把a和b都置为null

程序计数器各-1,不为0,但是都不能被调用。
PHP,Python都采用计数算法,搭配其他方案解决循环引用问题
以代码中特定的对象作为遍历的起点,也称为GCRoots,进行遍历,来判定某个对象是否能被访问到到。每遍历一个对象,就标记为“可达”,剩余没有被标记的就认为是不可达,就是接下来要回收的垃圾了。
可作为GC Roots的对象:
垃圾清除有四种算法:
标记-清除:把垃圾对象的内存直接进行释放缺陷:会产生“内存碎片问题”,很难找到大块的连续的内存空间
复制算法:把内存分为两部分,起始都保存在A区域;当垃圾回收时,把不释放的数据复制到B区域,再把A区域整体释放。这样就确保空闲内存都是连续的缺陷:内存空间利用率低,直接砍半;如果不需要释放的数据很多,复制开销很大
标记-整理:类似于顺序表删除,把不需要释放的数据重新搬运整理缺陷:内存搬运数据的操作开销也很大,只解决了空间成本问题。
分代收集:综合前三种方案。“代”指的是对象的年龄,单位是GC的轮次。
堆区域划分:
一个对象经历的区域:伊甸园 -> 幸存区->幸存区->...->幸存区->老年代 新生代统一采用复制算法;老年代采用标记整理

特殊情况:如果某个对象很大,复制成本很高,可以跳过新生代,进入老年代
在JVM中,由垃圾回收器来实现上述分代回收的策略。
分代回收只是最基本的策略,落实到垃圾收集器上,还有更特殊和进阶的策略。
| 收集器 | 分代 | 特点 | 使用场景 |
|---|---|---|---|
| Serial | 新生代 | 单线程串行,STW | Client客户端、小内存程序 |
| ParNew | 新生代 | Serial多线程版,唯一可配合CMS | 老年代CMS搭配使用 |
| Parallel Scavenge | 新生代 | 吞吐量优先,自适应调参 | 后台计算、无交互服务 |
| Serial Old | 老年代 | 单线程标记整理 | Client、CMS失败后备方案 |
| Parallel Old | 老年代 | 多线程吞吐量优先 | 高吞吐离线任务 |
| CMS | 老年代 | 并发低停顿,标记清除,碎片多 | JDK8前互联网低延迟服务 |
| G1 | 全堆分区Region | 可预测停顿,自动压缩,替代CMS | 大内存服务端,JDK9默认 |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。
您可能感兴趣的文章:AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇 心理映射镜头
开源的AI论文翻译/PDF格式转换工具:这两款AI神器完美翻译PDF论文
AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇 特殊效果镜头
AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇(电影空间的建构)
最近爆火的AI制作美女健身视频:AI生成俯卧撑女孩视频教程
怎样免费下载即梦AI无水印高清图片?关于即梦AI使用的小技巧