Java
本文最后更新于 2025年9月12日 中午
JVM内存结构
JVM 执行 Java 程序时会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建和销毁时间。
1、程序计数器(Program Counter Register)
当前线程所执行的字节码的行号指示器。字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
特点:
线程私有:每条线程有个独立的PCR,互不影响,独立存储。
如果正在执行Java方法,PCR的值为正在执行的JVM的字节码指令地址;如果是Native方法,值为空。
唯一一个没有
OutOfMemoryError情况的区域
2、Java虚拟机栈(Java Virtual Machine Stacks)
Java 方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧 (Stack Frame), 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法从调用到完成的过程,对应着一个栈帧在JVM栈中从入栈到出栈的过程。
特点:
- 线程私有
- 存储内容:局部变量表,编译期可知的基本数据类型,对象引用类型和返回地址类型。
- 异常:
StackOverflowError,OutOfMemoryError
3、本地方法栈(Native Method Stack)
与虚拟机栈非常相似,区别是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。HotSpot 虚拟机直接把本地方法栈和虚拟机栈合二为一。
特点和虚拟机栈类似。
4、Java堆(Java Heap)
此内存区唯一的目的就是存放对象实例,基本在这里分配内存,通常所说的堆内存就是Java堆。
特点:
- 线程共享:Java 堆是虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建。
- GC 堆:Java 堆是垃圾收集器管理的主要区域,也被称为”GC堆“。
- 异常:
OutOfMemoryError
5、方法区(Method Area)
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
特点:
- 线程共享
- 异常:
OutOfMemoryError
6、运行时常量池(Runtime Constant Pool)
方法区的一部分,常量池表 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的RCP中。
特点:
- 动态性,并非预置入 Class 文件常量池的内容才能进入RCP,运行期间也可能将新的常量放入池中。
7、直接内存(Direct Memory)
并不是虚拟机运行时数据区的一部分,但也有可能导致OutOfMemoryError。
Java内存模型(JMM)
抽象的概念,并不真实存在,核心目标是解决多线程环境下的三个问题:
- 原子性:一个操作或多个操作要么全部执行成功,要么全部不执行。
- 可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
- 有序性:程序执行的顺序按照代码的先后顺序执行。
规定了:
- 所有的变量都在主存(MM)中
- 每条线程还有自己的工作内存(WM),WM中保存了被该线程使用到的变量的MM副本拷贝。
- 线程对变量的所有操作(读取、赋值等)都必须在WM中进行,而不能直接读写MM中的变量。
- 不同的线程之间也无法直接访问对方WM中的变量,线程间变量值的传递均需要通过MM来完成。
当一个线程操作变量时,流程通常是:
- 从MM中拷贝变量副本到WM
- 在WM中修改副本的值
- 在某个时间点,将WM的值刷新回MM
volatile
轻量级同步机制,主要解决了可见性和有序性,不保证原子性,开销低,仅能修饰变量。
保证可见性:当一个变量A被声明为volatile后。
- 写:对
A的写操作会立刻强制刷新到MM。 - 读:对
A的读操作会强制从MM中重新读取最新的值。
保证有序性:通过插入内存屏障来禁止指令重排序。
- 写:写之前的所有普通变量的写操作都完成并且已经刷新到MM。
- 读:读之后的所有后续普通变量的读/写操作都不会被重排序到该读操作之前。
局限:不保证原子性,可以用synchronized保证原子性。
JAVA对象引用模式
Java 的引用模式是其内存管理和对象访问的核心,Java通过引用来间接操作所有对象。
对象:堆内存中实际存在的数据实体,包含了其所属类的所有成员的变量的值。
引用:一个变量,其值是对象在堆内存中的地址,引用本身存储在栈内存或另一个对象中。
关键点:
new在堆上创建对象,并返回该对象的地址。=是复制地址值并非复制对象,多个引用指向同一个对象。null不指向任何对象。
参数传递:
Java中只有按值传递,传递的是地址的副本,并非引用更不是对象。
equals()和==的区别
| 特性 | equlas |
== |
|---|---|---|
| 本质 | 操作符 | 方法(定义在Object类中) |
| 比较对象 | 内存地址 | 内容(需要重写,比如String,Integer) |
| 比较基本类型 | 实际值 | 不能用于基本类型 |
| 是否可自定义 | 否 | 是 |
ArrayList和LinkedList的区别
| 特性 | ArrayList |
LinkedList |
|---|---|---|
| 底层数据结构 | 动态数组 | 双向链表 |
| 获取元素 | O(1) 支持随机访问 |
O(n) 从表头或者表尾开始遍历 |
| 插入/删除元素 | O(n) | O(1) |
| 遍历效率 | 高 | 低 |
| 内存占用 | 小 | 大 |
| 应用场景 | 读多写少,绝大多数情况 | 写多读少,栈,队列等 |
接口
一种抽象类型,定义类必须实现的方法的集合,但接口本身不能有方法的具体实现(Java8之后default和static可以实现),主要提供一种规范,使得不同类可以通过统一接口进行交互,而不关心具体实现。
实现接口:implements
特点:
- 多实现
- 解耦:减少类之间的耦合
- 多态:实现运行时多态
函数覆写
子类重新定义从父类继承而来的同名函数,以实现不同的功能。
要求:
- 方法签名相同:函数名,参数个数、类型都必须相同。
# 参数列表必须完全相同,不一致则变成函数重载(overload)
- 访问权限:子类方法的访问权限不能比父类更严格。
- 多态体现:当父类指针/引用调用函数时,会根据实际对象类型执行子类方法(支持虚函数/动态绑定)。
关键字:virtual,override
Java泛型
写一个类或者方法时,不指定具体的数据类型,而是用一个类型参数来代替,使用时再指定具体类型,减少强制类型转换的需要。
1 | // 定义泛型类 |
不止是类,还可以用于泛型接口,方法等。
优点:
- 类型安全
- 减少强制转换
- 可读性强
# 不能用于基本类型
常用场景:集成框架,工具类方法,通用对象封装。