Java虚拟机(JVM)是一个运行Java程序的虚拟环境, 它负责将字节码转换为机器码并执行.
JVM组成部分:
1. 类加载器子系统
类加载器的定义
Java的类加载器是JVM中用于动态加载类文件的组件, 它将.class文件中的字节码加载到内存中, 并转换为Class对象供JVM执行.
类加载器的作用
动态加载类
在运行时按需加载类, 而不是在编译时加载所有类
隔离不同的类命名空间
通过不同的类加载器, 可以隔离同名类, 避免冲突
三种类加载器
启动类加载器: JVM的一部分, JDK9之前由C++实现, 之后由Java实现
扩展类加载器: Java实现, 独立于虚拟机, 主要加载系统变量指定的类库
应用程序类加载器: Java实现, 主要负责加载用户类路径上的类库, 应用程序的默认加载器
自定义类加载器
通过继承ClassLoader类重写findClass方法, 可以自定义类加载器
应用场景: 例如热部署的动态重载
双亲委派模型
工作流程
当类加载器试图加载某个类时, 会先逐层向上委派给父类加载器去加载, 直到根类加载器. 当逐层都无法加载时, 才由当前类加载器自行加载
为什么有双亲委派模式
避免重复加载同一个类, 保障核心类库不被篡改
类加载器的工作流程
加载
将字节码内容读取到内存中, 生成class对象
校验
验证加载进来字节码内容的是否符合规范
准备
为静态变量赋初始值(注意, 是初始值, 例如int的初始值为0), 为静态变量在方法区划分内存空间
解析
将常量池的符号引用转为直接引用(例如: 让A做xxx, 变成让小明买菜)
初始化
执行一些静态代码块, 为静态变量赋值(注意, 这里才是真正的代码中赋值)
2. 运行时数据区(JVM内存模型)
方法区
存储类信息, 常量, 静态变量
属于线程共享区域, 所有线程共享方法区内存
JDK8之前使用永久代来实现方法区, 从JDK8开始被元空间取代(元空间使用本地内存), 相当于更换接口的实现类(方法区接口由永久代实现改为元空间实现)
堆
用于存放所有线程共享的对象和数组, 是GC的主要区域
新生代
新生代存在一个Eden区和两个交替使用的Survivor区(S0和S1)
新对象会被分配到Eden区, Eden区较大, 且GC频繁
新对象在Eden区经过一次GC后存放到其中一个Survivor区, 年龄+1
当Survivor区的对象年龄达到阀值(默认15)时, 会晋升到老年代
年龄最大限制15的原因: 年龄信息存储在对象头中, 大小4位, 二进制最大值为1111, 对应10进制的15
老年代
Major GC / Full GC 会在老年代进行, 但频率较低
永久代 / 元空间
永久代GC复杂, 且GC效率低
从JDK8开始, 永久代被元空间取代, 直接使用本地内存
虚拟机栈
虚拟机按照先进后出的方式存储所有非本地方法调用的栈帧
每个线程创建一个栈帧, 栈帧是线程私有的, 生命周期与线程相同
栈帧中存储局部变量(存储基本数据类型以及对象引用, 如int ,float等), 操作次数栈, 动态链接, 方法返回地址等
递归调用时可能导致压栈太深而溢出, 若虚拟机无法动态扩展或者申请到足够内存, 会报内存不足
本地方法栈
结构与虚拟机栈一致, 且线程私有
为本地方法服务, 使用JNI调用的本地代码在此区域分配内存
在HotSpot虚拟机中, 虚拟机栈和本地方法栈已合并
程序计数器
一个小的内存区域, 保存当前线程执行的字节码指令的地址和行号
每个线程都有一个独立的计数器, 属于线程私有
唯一不会出现OOM的区域
3. 执行引擎
执行引擎的定义
执行引擎将字节码转换为机器码并执行
执行引擎的组成
解释器
逐行解释字节码并执行, 适用于程序初次运行
即时编译器JIT
将热点代码编译为机器码并缓存, 提升执行效率
垃圾回收器
自动回收堆内存中不再使用的对象, 是JVM内存管理的核心
4. 本地方法接口(JNI)
本地方法接口
提供了一个桥梁, 用于调用C/C++等语言编写的本地库
本地方法库
本地库的实现, 是JVM实现某些底层功能(线程操作, 网络通信等)的基础