Java基础 - JVM
什么是JVM
Java的理念是:Write Once, Run Anywhere。Java编译器把 Java 程序编译成“字节码”(.class 文件),字节码可以在所有平台运行,这是JAVA特性之一。字节码是 JVM 能理解的“中间语言”,JVM可以把字节码翻译成你的电脑能理解的机器指令。因此只需要在不同的机器上安装适配不同架构的JVM,就可以实现 Write Once, Run Anywhere。
JVM的功能
- 跨平台运行
- 内存管理:JAVA帮助开发者垃圾回收,分配内存
- 性能优化:即时编译器(Just-In-Time ,JIT ),将热点代码(频繁执行的代码段)编译成本地机器码,以提高程序的执行性能
- 安全性:异常处理,类加载时字节码验证,沙箱环境等
JVM与OS进程的关系
-
JVM 本身就是一个普通的操作系统进程:JVM就是一个跑在 OS 之上的用户态程序(C/C++ 写的),当你执行
java Main时,操作系统启动了一个名为 java 的可执行文件。这个文件是编译好的机器码(主要是 C++ 写成的 HotSpot 虚拟机)。 -
Java 程序不是进程,JVM 进程里运行着 Java 程序。JVM 启动后,读取你的 .class 文件,把它视为“输入数据”,解析这些数据里的指令。
-
Native Memory (本地内存):这是操作系统分配给 JVM 进程的总内存地址空间。
-
Java Heap:JVM 向 Native Memory 申请并管理的最大一块连续内存区域,用于存储所有的 Java 对象实例和数组。
-
Java Stack (VM Stack):存储 栈帧 (Stack Frame)。每个 Java 方法被调用时,都会创建一个栈帧,用来存放局部变量表(基本类型值、对象引用)、操作数栈、动态链接等。
-
Native Method Stack:用于支持 native 方法(使用 C/C++ 编写的方法,如 JNI 调用)执行的栈。JVM 本身通常由 C++ 编写,当 Java 代码调用操作系统底层功能(如文件读写、网络 IO)时,必须通过 JNI (Java Native Interface) 进入 Native 层。
-
JVM 的 Garbage Collection 功能是由 GC 线程实现的。GC 线程本质上是 JVM 内部的 C++ 线程。
JVM的运行时数据区域
以JDK 1.8为例,
- 线程私有的:
- 程序计数器:当前线程执行的字节码位置
- Java 虚拟机栈:每个线程的JAVA方法的“方法调用栈”,包含栈帧
- 本地方法栈:给调用 C、C++ 等非 Java 方法(native 方法)用的栈
- 线程共享的
- 堆:用来存储所有 Java 对象实例,由垃圾回收器(GC)统一管理
- 元空间:用来存储类的元数据(类的信息、常量、静态变量、方法字节码等)。
类加载器
类加载器是指把类的字节码(通常是 .class 文件)加载到 JVM 的内存中,并生成相应的 Class 对象的工具。具体来说,类加载器把字节码(.class 文件)加载到 JVM 的方法区(Method Area)(Java 8 之后是 元空间(Metaspace)),然后在堆中创建一个 Class 对象来存储类的信息,方便反射操作。
类并不是一开始就全部加载。类加载器可以按需加载:当你第一次使用某个类时,它才去加载它。
public class Person {
static List<String> list = new ArrayList<>();
static {
System.out.println("Person 类被初始化");
}
int age = 20;
public void sayHello() {
System.out.println("Hello");
}
}
根据以上的例子,类加载器执行过程:
- 加载:类加载器通过类的名字 Person 找到 Person.class 文件,将 .class 文件的字节码加载到 JVM,并在堆中生成一个代表这个类的 java.lang.Class 对象作为数据访问的入口
- 验证:检查字节码是否合法
- 准备:为静态变量(
List<String> list)分配内存,并赋初始值 null(此时静态变量只是占位,还没初始化) - 解析:将符号引用替换为直接引用,比如将
System.out.println方法名解析为真正的内存地址 - 初始化:执行静态代码块,给静态变量赋值(这里的list变量本身在方法区,指向的HashMap对象实例在堆里创建)
Class对象
public final class Class {
private Class() {}
}
比如,当JVM加载String类时,会执行Class cls = new Class(String);在堆中创建Class对象。这个对象包含类名、包名、父类、实现的接口、所有方法、字段等对应class的所有信息。内存分布:
Java 堆:
┌────────────────────────────┐
│ Class<Person> clazz │
│ ── name: "Person" │
│ ── loader: AppClassLoader │
│ ── klass_pointer →────────────┐
└────────────────────────────┘ │
|
方法区(Metaspace): ▼
┌────────────────────────────┐
│ Class Metadata (Klass*) │
│ ── className: "Person" │
│ ── superClass: java.lang.Object
│ ── interfaces: ... │
│ ── methods[]: sayHello() │
│ ── fields[]: name │
│ ── constantPool: ... │
│ ── annotations: ... │
└────────────────────────────┘
JAVA对象在内存中的结构
Java 对象几乎都在堆上,引用变量通常在栈上。
-
对象头
包含:- 标记字段(Mark Word):指向哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID等。
- 类型指针(Klass pointer):指向类的元数据,是一个在元空间的C++结构,与上文Class对象的klass_pointer指向的结构一样。
-
实例数据(成员变量,如果是 Primitive Type 则保存值本身,如果是 Reference Type 则保存对象地址)
-
对齐填充(JVM 要求对象大小必须是 8 的倍数)