JVM 概述
Java Virtual Machine (JVM) 是 Java 语言的运行环境,负责加载、验证、解释和执行 Java 字节码。
.class 文件通过 JVM 转换到各个平台可以执行的机器指令。
JVM 结构
JVM 由类加载系统、运行时数据区和执行引擎构成。
类加载系统
类加载系统首先读取指定的类文件,并遵循双亲委派机制进行加载。
双亲委派机制
类加载请求:
- 当某个类加载器(如应用类加载器)收到加载某个类的请求时,它不会直接尝试加载该类。
向父加载器委派:
- 它会先将这个请求委派给它的父类加载器。
- 父类加载器再继续向它的父类加载器委派,直到到达顶层的 Bootstrap ClassLoader(启动类加载器)。
加载类:
- 顶层的 Bootstrap ClassLoader 会尝试加载该类。
- 如果它无法加载,则将控制权交还给子加载器,依次向下传递,直到找到能够加载该类的加载器。
加载顺序
- Bootstrap ClassLoader(启动类加载器):
- 加载核心类库(如 rt.jar 中的类)。
- Extension ClassLoader(扩展类加载器):
- 加载扩展类库(如 jre/lib/ext 目录下的类)。
- Application ClassLoader(应用类加载器):
- 加载应用程序的类(如 classpath 下的类)。
graph TD
A["BootStrapClassLoader(引导类加载器)"]
B["ExtensionClassLoader(扩展类加载器)"]
C["AppClassLoader(应用类加载器)"]
CC["ClassNotFoundException(类未找到异常)"]
D{"加载过?"}
D1{"加载过?"}
E{"可以加载"}
E1{"可以加载"}
E2{"可以加载"}
C -->D1
D1 --> B
B --> D
D --> A
A --> E
E --> B
B --> E1
E1 --> C
C --> E2
E2 --> CC
类加载过程
加载:
- 将文件中的常量池、字段、方法和指令加载到 JVM 内存的共享区域方法区中。
验证:
- 验证类文件的字节码是否符合 JVM 规范,确保类的正确性和安全性。
准备:
- 为类的静态变量分配内存空间,并设置默认值(如 0、null 等)。
解析:
- 常量池中表示对象的符号引用指向到实际的内存地址,也就是直接引用。
初始化:
- 执行类的静态代码块和静态变量的初始化。
执行引擎
工作模式
- 解释执行:逐条解释执行字节码指令。
- 即时编译(JIT):将热点代码编译为本地机器码,并缓存以提高执行效率。
主方法执行
- 静态代码块执行完成后,JVM 会继续执行主方法(main method)。
- 如果没有 main 方法,JVM 会报错。
调用 main 方法时,会在内存中分配线程私有的空间,包括程序计数器和栈帧。
内存管理
堆内存分代设计
Q3: 堆内存为什么要分代设计?
A3: 堆内存分代设计是为了提高垃圾回收的效率。根据对象的生命周期,将堆内存分为新生代和老年代。新生代中的对象大多数是短命的,垃圾回收频繁进行,而老年代中的对象相对长命,垃圾回收较少。通过分代收集,可以针对不同生命周期的对象采用不同的回收策略,从而提高整体性能。
对象内存分配过程
- 内存分配:在堆中为对象分配内存。
- 初始化:将对象的实例变量设置为默认值(如 0、null 等)。
- 执行构造函数:调用对象的构造函数,执行初始化逻辑。
- 返回对象引用:将对象的引用返回给调用者。
内存分配策略
碰撞指针法:
- 通过维护一个指针来指向当前可用内存的起始位置。
- 分配内存时,直接从指针位置分配所需大小的内存,然后更新指针位置。
- 优点:分配速度快。
- 缺点:容易导致内存碎片。
空闲法:
- 通过维护一个空闲内存列表来管理内存分配。
- 分配内存时,遍历空闲列表找到足够大的空闲块进行分配,并更新空闲列表。
- 优点:减少内存碎片。
- 缺点:分配速度相对较慢。
垃圾回收
后续流程
实例初始化后,会将对象的引用存储到局部变量表中(虚拟机栈),线程可以通过引用访问到该对象。后续的代码会继续按照这个流程工作。