一、虚拟机栈(常简称栈)
- 存储内容每个 Java 线程私有,线程创建时同步创建,线程销毁栈直接释放。 核心存储栈帧:每调用一个方法就压入一个栈帧,方法执行完毕栈帧出栈。 栈帧包含:局部变量表(基本数据类型、对象引用)、操作数栈、动态链接、方法返回地址。
- 核心特点
- 线程私有,不存在并发竞争;
- 内存分配连续,存取速度极快;
- 内存空间固定,栈深度超出会抛出
StackOverflowError; - 生命周期和线程绑定,自动回收,无需 GC。
- 举例
int a = 10;局部变量 a 存放在当前方法栈帧的局部变量表。
二、堆(Java 堆)
- 存储内容JVM 中唯一共享的内存区域,所有线程共用,是 GC 回收的主要区域。 存放所有对象实例、数组,只要 new 出来的对象,全部存在堆中。
- 核心特点
- 线程共享,多线程并发创建对象会有线程安全问题;
- 内存空间大,动态分配,GC 专门管理这块内存;
- 内存不足时抛出
OutOfMemoryError; - 内部划分为新生代、老年代,分代收集提升 GC 效率。
- 举例
User user = new User();new 出来的 User 实体对象存堆,变量 user 只是一个引用,存虚拟机栈。
三、方法区(元空间 MetaSpace,JDK8 重大改动)
JDK7 及以前:永久代
方法区使用堆内存实现,容易 OOM。
JDK8 及以后:元空间(MetaSpace)
直接使用本地内存,不再占用堆内存,几乎不会溢出。
- 存储内容线程共享区域,存放类的静态元数据:
- 加载后的 Class 字节码信息(类名、字段、方法、接口);
- 运行时常量池(字符串常量、字面量、符号引用);
- 静态变量 static、static 代码块;
- 即时编译器 JIT 编译后的代码缓存。
- 核心特点
- 线程共享,类加载时写入,程序运行全程存在;
- JDK8 元空间使用操作系统本地内存,可动态扩容;
- 只有类卸载、程序关闭才会释放内存;
- 存放静态资源,和实例对象完全隔离。
- 举例
public static String name = "张三";static 变量 name、字符串常量 “张三” 都存放在方法区。
Class 类元信息 和 new 实例对象 的核心区别
一、先分别说清楚两者是什么
1. Class 字节码元信息(存在方法区 / 元空间)
当 JVM 通过类加载器加载User.class文件后,会解析字节码,把这个类本身的模板信息存到方法区。 存储内容:
- 类基础信息:类名、父类、实现的所有接口、访问修饰符;
- 所有字段模板:字段名、类型、修饰符(static/final/private),不存字段具体值;
- 所有方法模板:方法名、参数列表、返回值、方法字节码、修饰符;
- 常量池、注解、静态变量。
简单理解:这是一张 User 的图纸、模板,只定义有什么属性、有什么方法,没有真实数据。
2. new User () 实体对象(存在 Java 堆)
执行User user = new User()时,在堆中开辟一块内存,创建类的实例。 存储内容:
- 对象头:mark word、类型指针(指向方法区的 User Class 模板);
- 实例字段真实数据:每个成员变量的具体值;
- 不存在方法副本:对象不会存储方法,调用方法时会去方法区找 Class 模板里的方法。
简单理解:按照图纸造出来的真实产品,有属于自己的属性数值。
二、核心区别对比(面试重点)
表格
| 对比维度 | 加载后的 Class 字节码信息 | new 出来的 User 实例对象 |
|---|---|---|
| 存放位置 | 方法区(元空间),线程共享 | Java 堆,线程共享 |
| 本质 | 类的模板 / 定义图纸 | 模板实例化后的真实实体 |
| 存储内容 | 类结构定义:字段模板、方法代码、类名、静态变量;无实例属性值 | 对象真实数据:实例变量的具体数值、对象头;不存储方法 |
| 数量 | 一个类只会加载一份Class 模板,全局唯一 | 可以 new 无数个对象,每个独立堆内存 |
| 生命周期 | JVM 启动加载类后一直存在,直到类卸载、程序关闭 | 对象无任何引用时,会被 GC 回收销毁 |
| 访问方式 | 通过反射 Class<User>获取 | 通过对象引用 user 访问 |
| 静态资源归属 | 存放 static 静态变量,所有对象共用 | 不存放静态变量,只存独有的实例变量 |
三、举代码例子直观区分
java
运行
class User{ public static String className = "用户类"; // 静态变量 → Class模板(方法区) private String name; // 字段模板存在Class,真实值存在实例 private Integer age; public void sayHi(){} // 方法代码只存在Class模板 }- Class 模板(方法区)存储: User 类名、父类 Object、字段模板 name/age、sayHi 方法字节码、静态变量
className="用户类"。 - 两个实例对象(堆内存):
java
运行
User u1 = new User(); u1.name = "张三"; u1.age = 18; User u2 = new User(); u2.name = "李四"; u2.age = 20;- u1 堆内存:name="张三",age=18;
- u2 堆内存:name="李四",age=20; 两个对象独立,各自存储自己的属性值; 调用
u1.sayHi()时,不会自带方法,而是通过对象头的类型指针,找到方法区里 User 的 Class 模板执行方法。
四、一句话总结(面试收尾加分)
Class 字节码信息是类的通用模板,全局只存一份,定义类有哪些属性和方法;而 new 创建的实例是模板的具象产物,每个对象独立保存自身属性值,堆中可以创建多个。
五、三者核心区分总结(面试加分)
- 私有 / 共享栈:线程私有;堆、方法区:线程共享。
- 存储数据栈:局部变量、对象引用; 堆:new 出来的实例、数组; 方法区:Class 类信息、静态变量、常量池。
- 内存回收栈:线程销毁自动释放,无 GC; 堆:GC 重点回收区域; 方法区:仅类卸载时回收。
- 速度栈 > 方法区 > 堆。
高频面试补充考点
- 局部变量存在栈,线程安全;对象实例在堆,多线程操作会有并发问题;
- 引用在栈,真实对象在堆,栈里的引用指向堆内存地址;
- 静态变量不属于对象,归属于类,因此放在方法区,所有对象共享同一份静态变量。