JVM
JVM,也就是 Java 虚拟机,它最主要的作用就是对编译后的 Java 字节码文件逐行解释,翻译成机器码指令,并交给对应的操作系统去执行。
JVM 的其他特性有:
- JVM 可以自动管理内存,通过垃圾回收器回收不再使用的对象并释放内存空间。
- JVM 包含一个即时编译器 JIT ,它可以在运行时将热点代码缓存到 codeCache 中,下次执行的时候不用再一行一行的解释,而是直接执行缓存后的机器码,执行效率会大幅提高。
bash
注:JIT 和 JNI 的区别:
JIT 是"即时编译"(Just-In-Time Compilation)的缩写。
它是一种在程序运行时将字节码(bytecode)编译成机器码的技术,主要用于提高程序的执行效率。
JNI 是Java 提供的一种编程框架,允许 Java 代码与用其他语言(如 C、C++)编写的本地代码交互。
通过 JNI,Java 程序可以调用本地方法(native methods),这些方法通常是用 C 或 C++ 实现的,
并编译为动态链接库(DLL 或 SO 文件)。反过来,本地代码也可以调用 Java 方法
- 任何可以通过 Java 编译的语言,比如说 Groovy、Kotlin、Scala 等,都可以在 JVM 上运行。
bash
"通过 Java 编译"在问题中的含义是指"编译成 Java 字节码",每种语言都有自己的专用编译器。
JVM的组织架构
JVM 要解释执行需要进行三个步骤:加载 .class 文件 -> 准备数据 -> 执行
因此 JVM 大致可以划分为三个部分:类加载器、运行时数据区和执行引擎。

JVM内存模型
Java 虚拟机(JVM)在运行 Java 程序时,会将内存划分为若干区域,每个区域有其特定的功能。
-
程序计数器(Program Counter Register)
- 作用:记录当前线程正在执行的字节码指令的地址,用于控制程序的执行流程。
- 特点:每个线程都有独立的程序计数器,线程之间互不干扰。
-
Java 虚拟机栈(Java Virtual Machine Stacks)
- 作用:为 Java 方法(非 native 方法)的执行提供内存空间。
- 特点:每个线程拥有自己的虚拟机栈,栈中包含多个栈帧(Stack Frame)。每个栈帧对应一次方法调用,存储局部变量表、操作数栈、动态链接和方法出口等信息。
-
本地方法栈(Native Method Stacks)
- 作用:为 native 方法(用 C/C++ 等语言编写的方法)提供内存支持。
- 特点:与 Java 虚拟机栈类似,但专用于 native 方法的执行。
-
Java 堆(Java Heap)
- 作用:存储对象实例和数组。
- 特点:JVM 中最大的内存区域,所有对象实例都在堆上分配内存。堆是垃圾收集器(GC)的主要管理区域。
-
方法区(Method Area)
- 作用:存储类的元数据信息(如类结构)、静态变量、常量以及即时编译器编译后的代码。
- 特点:在 JDK 8 及之后,方法区被元空间(Metaspace)取代,元空间使用本地内存而非 JVM 堆内存。
-
运行时常量池(Runtime Constant Pool)
- 作用:存储编译期生成的字面量(如字符串常量)和符号引用。
- 特点:是方法区的一部分,包含每个类的常量池表。JDK 8 方法区变元空间,运行时常量池就放在堆上。
-
直接内存(Direct Memory)
- 作用:不属于 JVM 运行时数据区,但常用于 NIO(如 ByteBuffer)等场景。
- 特点:使用本地内存,不受 JVM 堆大小限制。
内存区域变化
主要是方法区到元空间,以及常量池的变化
字符串常量池,类常量池,运行时常量池存储的都是什么啊?
1.字符串常量池
- 字符串常量池主要存储 字符串字面量,也就是在 Java 代码中用双引号括起来的字符串,例如 "Hello"、"World" 等。
- 它的设计目的是为了复用这些字符串对象,确保 JVM 中每个唯一的字符串字面量只有一份,从而节省内存。
- String s1 = "Hello"; String s2 = "Hello";用 s1 == s2 验证,结果为 true
- 用 new String("Hello") 创建字符串,这个对象会分配在堆上,可以通过 String.intern() 方法将它放入字符串常量池。
2.类常量池
- javac 将源文件编译成 .class 文件,类常量池指的是这个文件的一部分,是在磁盘上的。
- 类常量池存储了在编译时生成的 字面量 和 符号引用
- 类加载阶段JVM 加载 .class 文件 时,会把类常量池的内容 拷贝到方法区(JDK 8+ 在元空间)
- 在解析阶段,逻辑地址会被替换为 实际的内存地址,部分数据进入 运行时常量池。
例如对于一个类:
java
package example;
import utils.Helper; // 🔹 导入外部类
public class Main {
// 🔹 静态常量
static final double PI = 3.14159;
// 🔹 实例变量
private String name;
// 🔹 构造方法
public Main(String name) {
this.name = name;
}
// 🔹 普通方法
public void greet() {
System.out.println("Hello, " + name);
}
public static void main(String[] args) {
// 🔹 创建对象
Main obj = new Main("Alice");
obj.greet();
// 🔹 调用外部类方法
Helper.sayHello();
}
}
类常量池为:
java
CONSTANT_Class example/Main
CONSTANT_Class utils/Helper // 🔹 外部类
CONSTANT_Fieldref example/Main.PI
CONSTANT_Fieldref example/Main.name
CONSTANT_Methodref example/Main.<init> // 🔹 构造方法
CONSTANT_Methodref example/Main.greet // 🔹 方法
CONSTANT_Methodref utils/Helper.sayHello // 🔹 外部类方法
CONSTANT_String "Hello, "
CONSTANT_String "Alice"
CONSTANT_Double 3.14159
3.运行时常量池
- 运行时常量池是 JVM 在运行时为每个类或接口维护的常量池
- 运行时常量池支持动态链接和运行时解析,例如将对 System.out.println 的符号引用解析为具体的对象和方法地址。
- 它还能在程序运行时扩展,例如添加新的字符串常量
- 运行时常量池 = 类常量池内容 + 直接引用 + 动态常量
堆内存
堆 是Java虚拟机(JVM)中内存管理的一个重要区域,主要用于存放对象实例和数组。随着JVM的发展和不同垃圾收集器的实现,堆的具体划分可能会有所不同,但通常可以分为以下几个部分:
- 新生代 :新生代又被划分为 Eden 空间和两个 Survivor 空间(From 和 To)
- 新创建的对象会被分配到 Eden 空间。
- Eden 区填满时,会触发一次 Minor GC,清除不再使用的对象。存活下来的对象会从 Eden 区移动到 Survivor 区
- 老年代:对象在新生代中经历多次 GC 后,如果仍然存活,会被移动到老年代。当老年代内存不足时,会触发 Major GC,对整个堆进行垃圾回收。
- 大对象区:在某些JVM实现中(如G1垃圾收集器),为大对象分配了专门的区域,这部分区域在老年代。
对象的内存布局
对象的内存布局是由 Java 虚拟机规范定义的,但具体的实现细节各有不同,如 HotSpot 和 OpenJ9 就不一样。HotSpot:
对象四种引用
四种分别是"强、软、弱、虚" 。
- 强引用:Object obj = new Object(); 只要存在就不回收;
- 软引用:SoftReference softRef =
new SoftReference<>(new Object())
; 内存不足回收,常用于实现内存敏感的缓存(如图片缓存),在内存压力大时自动清理; - 弱引用:WeakReference weakRef =
new WeakReference<>(new Object());
一定回收,临时引用,避免内存占用,比如 threadlocal 里的 key; - 虚引用:PhantomReference phantomRef =
new PhantomReference<>(new Object(), queue);
通常与 ReferenceQueue 结合使用,当对象被回收时,虚引用会被放入关联的 ReferenceQueue,在对象被回收时收到通知。虚引用不可达; - Java.lang.ref 包下的类
内存泄漏和内存溢出
用一个比较有味道的比喻来形容就是,内存溢出是排队去蹲坑,发现没坑了;内存泄漏,就是有人占着茅坑不拉屎,导致坑位不够用。
- 内存泄漏举例:1、静态属性导致内存泄露 2、 未关闭的资源 3、 使用ThreadLocal
- 静态属性(用 static 修饰的字段)属于类级别,其生命周期与类的加载和卸载绑定,其超长的生命周期和全局可见性导致更容易内存泄漏
ThreadLocal---java