Java

本文最后更新于 2025年12月1日 上午

JVM内存结构

JVM 执行 Java 程序时会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建和销毁时间。

1、程序计数器(Program Counter Register)

当前线程所执行的字节码的行号指示器。字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

特点:

  • 线程私有:每条线程有个独立的PCR,互不影响,独立存储。

  • 如果正在执行Java方法,PCR的值为正在执行的JVM的字节码指令地址;如果是Native方法,值为空。

  • 唯一一个没有OutOfMemoryError情况的区域

2、Java虚拟机栈(Java Virtual Machine Stacks)

Java 方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧 (Stack Frame), 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法从调用到完成的过程,对应着一个栈帧在JVM栈中从入栈到出栈的过程。

特点:

  • 线程私有
  • 存储内容:局部变量表,编译期可知的基本数据类型,对象引用类型和返回地址类型。
  • 异常:StackOverflowErrorOutOfMemoryError

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保证原子性。

并发编程

如何创建线程池,线程池常见参数有哪些?

通过Executors工厂类快速创建和通过ThreadPoolExecutor手动配置。后者能够实现精细化控制。

1、通过Executors工厂类快速创建:

  • 固定大小线程池(负载稳定,例如API)
1
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 核心线程数=最大线程数=5
  • 可缓存线程池(短周期,例如批量数据)
1
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // 线程数动态调整
  • 单线程线程池(事务处理)
1
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  • 定时/周期性线程池(周期性,例如心跳检测)
1
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

2、通过ThreadPoolExecutor手动配置:

1
2
3
4
5
6
7
8
9
10
11
12
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler
);

3、常见参数

参数 作用
corePoolSize 核心线程数,线程池长期保持的最小线程数
maxPoolSize 最大线程数,线程池允许的最大并发线程数
keepAliveTime 非核心线程空闲存活时间
unit keepAliveTime的时间单位,常用TimeUnit.SECONDS
workQueue 任务队列,存放待执行的任务,优先使用有界队列
threadFactory 线程工厂,用于创建线程,自定义线程名称、优先级等
handler 拒绝策略,当队列满且线程数达上限时的处理方式(异常等)

集合框架

HashMap底层原理和扩容机制

1、底层原理:

底层是一个哈希表(动态扩容的数组),数组的每个元素是一个链表/红黑树(JDK8 及以后),用于存储哈希冲突的键值对。

  • 桶:数组的每个槽位(table[i]),存放哈希值相同的键值对。
  • 哈希冲突:不同键通过哈希函数算出相同的下标,要放在一个桶中。
  • 红黑树:链表->红黑树,将查找时间从O(n)优化到O(logn)。

2、哈希冲突解决:

使用链地址法解决冲突:将冲突的键值对以链表形式挂在同一个桶下,尾插法,避免扩容死循环。

3、扩容机制:

通过resize使得负载因子在合理范围内,避免哈希冲突过多导致性能下降。

当元素数量size超过容量capacity×负载因子loadFactor时触发扩容,先创建新数组,容量翻倍,再迁移旧的键值对到新的桶中。

JAVA对象引用模式

Java 的引用模式是其内存管理和对象访问的核心,Java通过引用来间接操作所有对象。

对象:堆内存中实际存在的数据实体,包含了其所属类的所有成员的变量的值。

引用:一个变量,其值是对象在堆内存中的地址,引用本身存储在栈内存或另一个对象中。

关键点:

  • new在堆上创建对象,并返回该对象的地址。
  • =是复制地址值并非复制对象,多个引用指向同一个对象。
  • null不指向任何对象。

参数传递:

Java中只有按值传递,传递的是地址的副本,并非引用更不是对象。

equals()==的区别

特性 equlas ==
本质 操作符 方法(定义在Object类中)
比较对象 内存地址 内容(需要重写,比如String,Integer
比较基本类型 实际值 不能用于基本类型
是否可自定义

ArrayListLinkedList的区别

特性 ArrayList LinkedList
底层数据结构 动态数组 双向链表
获取元素 O(1)
支持随机访问
O(n)
从表头或者表尾开始遍历
插入/删除元素 O(n) O(1)
遍历效率
内存占用
应用场景 读多写少,绝大多数情况 写多读少,栈,队列等

接口

一种抽象类型,定义类必须实现的方法的集合,但接口本身不能有方法的具体实现(Java8之后defaultstatic可以实现),主要提供一种规范,使得不同类可以通过统一接口进行交互,而不关心具体实现。

实现接口:implements

特点:

  • 多实现
  • 解耦:减少类之间的耦合
  • 多态:实现运行时多态

函数覆写

子类重新定义从父类继承而来的同名函数,以实现不同的功能。

要求:

  • 方法签名相同:函数名,参数个数、类型都必须相同。

# 参数列表必须完全相同,不一致则变成函数重载(overload)

  • 访问权限:子类方法的访问权限不能比父类更严格。
  • 多态体现:当父类指针/引用调用函数时,会根据实际对象类型执行子类方法(支持虚函数/动态绑定)。

关键字:virtualoverride

Java泛型

写一个类或者方法时,不指定具体的数据类型,而是用一个类型参数来代替,使用时再指定具体类型,减少强制类型转换的需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 定义泛型类
public class Box<T> {
private T content;

public void setContent(T content) {
this.content = content;
}

public T getContent() {
return content;
}

}

// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent(); // 不需要强转

Box<Integer> intBox = new Box<>();
intBox.setContent(123);
Integer num = intBox.getContent();

// 注意用于方法时,想返回这个Box,必须要类似public static <T> box<T> success(T data)
// 必须提前声明<T>,不然会报错。

不止是类,还可以用于泛型接口,方法等。

优点:

  • 类型安全
  • 减少强制转换
  • 可读性强

不能用于基本类型

常用场景:集成框架,工具类方法,通用对象封装。


Java
http://example.com/java/
发布于
2025年9月8日
更新于
2025年12月1日
许可协议