Java核心面试复习文档

7 阅读23分钟

目录


1. JVM 的组成

JVM 可以理解为 Java 程序运行时的虚拟计算机,主要由以下部分组成:

JVM
 ├── 类加载子系统 (ClassLoader Subsystem)
 ├── 运行时数据区 (Runtime Data Area)
 ├── 执行引擎 (Execution Engine)
 ├── 本地方法接口 (JNI)
 └── 本地方法库 (Native Libraries)

1.1 类加载子系统

类加载子系统负责把 .class 字节码文件加载到 JVM 中。

类加载过程:

加载 (Loading)
    │
    ▼
验证 (Verification)
    │
    ▼
准备 (Preparation)
    │
    ▼
解析 (Resolution)
    │
    ▼
初始化 (Initialization)
阶段说明
加载读取 .class 文件,生成 Class 对象
验证校验字节码是否合法
准备给静态变量分配内存,并设置默认值
解析将符号引用转换为直接引用
初始化执行静态变量赋值和 static 代码块

示例:

public static int num = 10;

执行过程:

准备阶段:num = 0
初始化阶段:num = 10

1.2 运行时数据区

运行时数据区是 JVM 内存结构的核心。

运行时数据区
 ├── 线程共享
 │    ├── 堆 (Heap)
 │    └── 方法区 (Method Area) / 元空间 (Metaspace)
 │
 └── 线程私有
      ├── Java 虚拟机栈 (JVM Stack)
      ├── 本地方法栈 (Native Method Stack)
      └── 程序计数器 (Program Counter Register)

1.2.1 堆 (Heap)

堆用于存放对象实例,是 GC 的主要区域。

User user = new User();

其中 new User() 创建出来的对象主要存放在堆中。

堆通常分为:

Heap
 ├── 新生代 (Young Generation)
 │    ├── Eden
 │    ├── Survivor From
 │    └── Survivor To
 │
 └── 老年代 (Old Generation)

1.2.2 方法区 / 元空间

方法区用于存放类级别信息。

常见内容:

内容
类信息
常量
静态变量
运行时常量池
JIT 编译后的代码

JDK 8 之后,永久代被移除,改为 Metaspace 元空间
元空间使用的是本地内存,不再直接使用 JVM 堆内存。


1.2.3 Java 虚拟机栈

每个线程都有自己的虚拟机栈。每调用一个方法,都会创建一个栈帧。

栈帧包含:

内容说明
局部变量表存放方法参数和局部变量
操作数栈方法计算过程中的临时数据
动态链接指向运行时常量池中的方法引用
方法返回地址方法执行完后返回的位置

常见异常:StackOverflowError,典型原因是递归过深。

public void test() {
    test();
}

1.2.4 程序计数器

程序计数器用于记录当前线程执行到哪一条字节码指令。

特点
线程私有
占用内存很小
唯一不会出现 OOM 的 JVM 区域

1.2.5 本地方法栈

本地方法栈用于执行 Native 方法。

private native void start0();

1.3 执行引擎

执行引擎负责执行字节码。

组件作用
解释器逐行解释执行字节码
JIT 编译器将热点代码编译成本地机器码
垃圾回收器 (GC)回收无用对象

1.4 -Xms 与 -Xmx 为什么设置一样大

-Xms 是 JVM 启动时的初始堆大小,-Xmx 是最大堆大小。

生产环境通常建议将两者设置为相同值,例如:

-Xms4g -Xmx4g

好处

好处说明
避免堆动态扩缩容的开销堆扩容时需要申请内存并可能触发 Full GC,缩容时也需要回收,影响性能
减少 GC 停顿次数堆大小稳定后,GC 策略和频率更加可预测,避免因扩容引发的意外 Full GC
避免内存碎片频繁扩缩容会加剧堆内存碎片化,固定大小有利于内存管理
防止因扩容失败导致的 OOM操作系统内存不足时,扩容可能失败,固定大小可提前发现问题
性能稳定可预测压测和生产环境堆大小一致,GC 行为一致,性能更稳定

堆扩容的代价

当 JVM 需要扩容堆时,会经历以下过程:

对象分配失败
    │
    ▼
触发 Minor GC
    │
    ▼
仍无法分配?
    ├── 否 ──► 正常分配
    │
    └── 是
          │
          ▼
        尝试扩容堆
          │
          ▼
        扩容成功?
          ├── 是 ──► 可能触发 Full GC(整理内存)
          │
          └── 否 ──► OOM

面试回答模板

生产环境建议将 -Xms-Xmx 设置为相同大小,主要出于三方面考虑:
第一,避免 JVM 运行时动态扩缩容带来的性能开销,扩容过程可能伴随 Full GC,影响响应时间;
第二,保证 GC 行为的稳定性和可预测性,堆大小固定后 GC 策略不会因堆变化而波动;
第三,提前暴露内存问题,如果设置的值不够用,压测阶段就能发现,而不是线上因扩容失败导致 OOM。


2. JMM 是什么

JMM 全称是 Java Memory Model,Java 内存模型

注意:JMM 不是 JVM 的内存区域划分,而是 Java 对多线程并发访问共享变量定义的一套规范。


2.1 JVM 内存结构和 JMM 的区别

对比项JVM 内存结构JMM
关注点JVM 内存区域如何划分多线程如何读写共享变量
解决问题对象、栈帧、类信息如何存储可见性、原子性、有序性
典型内容堆、栈、方法区、程序计数器volatilesynchronizedhappens-before

2.2 JMM 解决的三大问题

2.2.1 可见性

一个线程修改了共享变量,其他线程能否立即看到。

问题示例:

boolean flag = true;

while (flag) {
    // do something
}

如果另一个线程修改 flag = false;,当前线程不一定马上感知到。

解决:

private volatile boolean flag = true;

2.2.2 原子性

一个操作是否不可被中断。

示例:

count++;

这不是原子操作,实际包含三步:

1. 读取 count
2. 加 1
3. 写回 count

解决方式:

AtomicInteger count = new AtomicInteger();
count.incrementAndGet();

或者:

synchronized void incr() {
    count++;
}

2.2.3 有序性

编译器和 CPU 可能会进行指令重排序。

int a = 1;
int b = 2;

在单线程下不影响结果,但在多线程下可能产生并发问题。


2.3 happens-before 规则

happens-before 用来判断一个操作对另一个操作是否可见。

规则说明
程序顺序规则一个线程内,前面的操作 happens-before 后面的操作
锁规则unlock happens-before 后续对同一把锁的 lock
volatile 规则volatile 写 happens-before 后续 volatile 读
线程启动规则Thread.start() happens-before 线程内操作
线程终止规则线程内操作 happens-before Thread.join()
传递性A → B,B → C,则 A → C

2.4 volatile 的作用

能力是否支持
可见性
有序性
原子性

所以:

volatile int count = 0;
count++;  // 仍然不是线程安全的

3. Java 垃圾回收算法比较

3.1 对象如何判断可以被回收

Java 主要使用 可达性分析算法:从 GC Roots 出发,如果一个对象没有任何引用链可达,则可以被回收。

常见 GC Roots:

GC Roots 类型
虚拟机栈中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
Native 方法引用的对象
被 synchronized 锁持有的对象

3.2 标记-清除算法 (Mark-Sweep)

标记存活对象 ──► 清除未标记对象
优点缺点
实现简单会产生内存碎片
不需要移动对象清理效率不稳定

适合:早期老年代回收思路。


3.3 复制算法 (Copying)

存活对象复制到另一块内存 ──► 清空原来的区域
优点缺点
没有内存碎片浪费一部分内存空间
回收效率高存活对象多时复制成本高

适合:新生代(对象大多朝生夕死,存活对象少,复制成本低)。


3.4 标记-整理算法 (Mark-Compact)

标记存活对象 ──► 存活对象向一端移动 ──► 清理边界外内存
优点缺点
没有内存碎片移动对象成本较高
适合存活对象多的区域通常需要暂停用户线程

适合:老年代。


3.5 分代收集算法

核心思想:不同生命周期的对象,使用不同的 GC 算法。

区域特点常用算法
新生代对象存活率低复制算法
老年代对象存活率高标记-清除 / 标记-整理
元空间类元数据类卸载

3.6 常见垃圾回收器比较

垃圾回收器特点适用场景
Serial单线程,简单客户端、小内存
Parallel Scavenge吞吐量优先后台计算、大批量任务
CMS低停顿,老年代并发回收JDK 8 常见,但容易碎片化
G1Region 分区,兼顾吞吐和低停顿JDK 9+ 默认,服务端常用
ZGC超低延迟,支持大堆大内存、低延迟系统
Shenandoah超低停顿,并发整理低延迟服务

3.7 G1 的核心特点

G1 将堆划分为多个 Region:

Heap
 ├── Region 1
 ├── Region 2
 ├── Region 3
 └── ...

特点:

特点
支持可预测停顿时间
支持大堆内存
优先回收垃圾最多的 Region
减少内存碎片
适合服务端应用

常用参数:

-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-Xms4g -Xmx4g

4. Java 线程池配置

Java 线程池核心类:ThreadPoolExecutor

构造方法核心参数:

public ThreadPoolExecutor(
    int corePoolSize,                     // 核心线程数
    int maximumPoolSize,                  // 最大线程数
    long keepAliveTime,                   // 非核心线程空闲存活时间
    TimeUnit unit,                        // 时间单位
    BlockingQueue<Runnable> workQueue,    // 任务队列
    ThreadFactory threadFactory,          // 线程工厂
    RejectedExecutionHandler handler      // 拒绝策略
)

4.1 核心参数说明

参数说明
corePoolSize核心线程数
maximumPoolSize最大线程数
keepAliveTime非核心线程空闲存活时间
unit时间单位
workQueue任务队列
threadFactory线程工厂
handler拒绝策略

4.2 线程池执行流程

提交任务
    │
    ▼
核心线程数未满?
    ├── 是 ──► 创建核心线程执行
    │
    └── 否
          │
          ▼
        队列未满?
          ├── 是 ──► 放入队列
          │
          └── 否
                │
                ▼
              最大线程数未满?
                ├── 是 ──► 创建非核心线程执行
                │
                └── 否 ──► 执行拒绝策略

4.3 线程数如何配置

CPU 密集型任务

场景
加密解密
图片处理
大量计算
复杂规则计算

推荐:

线程数 = CPU 核心数 或 CPU 核心数 + 1

示例(8 核 CPU):

corePoolSize = 8;
maximumPoolSize = 9;

IO 密集型任务

场景
调用第三方接口
查询数据库
访问 Redis
文件上传下载
MQ 消费处理

推荐公式:

线程数 = CPU 核心数 × (1 + IO等待时间 / CPU计算时间)

简化估算:

线程数 = CPU 核心数 × 2 ~ 4

示例(8 核 CPU):

corePoolSize = 16;
maximumPoolSize = 32;

最终配置应结合压测、CPU 使用率、队列长度、响应时间综合调整。


4.4 队列如何选择

队列特点场景
ArrayBlockingQueue有界队列推荐,防止 OOM
LinkedBlockingQueue可近似无界不建议直接使用无界队列
SynchronousQueue不存储任务,直接交给线程高并发短任务
PriorityBlockingQueue支持优先级优先级任务
DelayQueue延迟执行延迟任务

生产建议:必须使用有界队列

不建议:

Executors.newFixedThreadPool(10);

原因:底层使用无界队列,任务堆积可能导致 OOM。


4.5 拒绝策略

策略说明
AbortPolicy直接抛异常(默认策略)
CallerRunsPolicy由提交任务的线程执行
DiscardPolicy直接丢弃任务
DiscardOldestPolicy丢弃队列中最老的任务

生产中常用:

new ThreadPoolExecutor.CallerRunsPolicy()

作用:对调用方形成反压,避免任务无限堆积。


4.6 生产级线程池示例

@Configuration
public class ThreadPoolConfig {

    @Bean("bizExecutor")
    public ThreadPoolExecutor bizExecutor() {
        int cpu = Runtime.getRuntime().availableProcessors();

        return new ThreadPoolExecutor(
                cpu * 2,
                cpu * 4,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000),
                new ThreadFactory() {
                    private final AtomicInteger index = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("biz-pool-" + index.getAndIncrement());
                        thread.setUncaughtExceptionHandler((t, e) ->
                                log.error("线程池执行异常, thread={}", t.getName(), e)
                        );
                        return thread;
                    }
                },
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

4.7 线程池配置原则

原则说明
不使用 Executors 默认工厂避免无界队列或无限线程
线程数根据任务类型配置CPU 密集型和 IO 密集型不同
队列必须有界防止任务无限堆积
线程必须命名方便日志排查
配置拒绝策略防止系统被打爆
增加监控监控 activeCount、queueSize、completedTaskCount
异常要捕获防止任务静默失败

5. execute 与 submit 对比

execute()submit() 都可以向线程池提交任务,但它们的能力不同。


5.1 核心对比

对比项execute()submit()
所属接口ExecutorExecutorService
是否有返回值有,返回 Future
支持 Runnable
支持 Callable
能否获取任务结果不能可以通过 Future.get() 获取
异常表现异常直接抛到 UncaughtExceptionHandler异常封装进 Future,调用 get() 时抛出
适合场景只执行任务,不关心结果需要结果、异常感知、任务取消

5.2 execute 支持 Callable 吗

不支持。

方法签名:

void execute(Runnable command);

所以 execute() 只能接收 Runnable

错误示例:

executor.execute(new Callable<String>() {
    @Override
    public String call() {
        return "success";
    }
});

原因:Callable 不是 Runnable,编译不通过。

正确用法:

executor.execute(() -> {
    System.out.println("执行 Runnable 任务");
});

5.3 submit 支持 Runnable 吗

支持。submit() 支持三种形式:

Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);

方式一:提交 Runnable,没有业务返回值

Future<?> future = executor.submit(() -> {
    System.out.println("Runnable task");
});

Object result = future.get();
System.out.println(result);  // null

方式二:提交 Runnable,并指定固定返回值

Future<String> future = executor.submit(() -> {
    System.out.println("Runnable task");
}, "success");

String result = future.get();
System.out.println(result);  // success

注意:"success" 不是任务内部计算出来的结果,而是提交任务时指定的固定值。


方式三:提交 Callable,有真实返回值

Future<String> future = executor.submit(() -> {
    return "callable result";
});

String result = future.get();
System.out.println(result);  // callable result

5.4 Runnable 和 Callable 对比

对比项RunnableCallable
方法run()call()
返回值无返回值有返回值
异常不能直接抛 checked exception可以抛 checked exception
常用提交方式execute() / submit()submit()

接口定义:

@FunctionalInterface
public interface Runnable {
    void run();
}
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

5.5 异常处理区别

execute 异常表现

executor.execute(() -> {
    int i = 1 / 0;
});

异常会直接抛出,线程会终止,通常能在日志中看到。


submit 异常表现

Future<?> future = executor.submit(() -> {
    int i = 1 / 0;
});

如果不调用 future.get(),异常可能不会明显暴露。

正确处理:

try {
    future.get();
} catch (ExecutionException e) {
    Throwable cause = e.getCause();
    cause.printStackTrace();
}

submit() 会把任务异常包装成 ExecutionException


5.6 submit 的底层逻辑

submit() 内部会将任务封装成 FutureTask,再调用 execute() 执行。

大致逻辑:

RunnableFuture<T> ftask = new FutureTask<>(callable);
execute(ftask);
return ftask;

因为 FutureTask 实现了 Runnable,所以最终仍然可以交给线程池执行。


6. OOM 问题如何处理

OOM 全称:OutOfMemoryError,表示 JVM 内存不足。


6.1 常见 OOM 类型

OOM 类型常见原因
Java heap space堆内存不足,对象太多
GC overhead limit exceededGC 频繁但回收效果差
Metaspace类太多,动态代理太多
Direct buffer memory直接内存不足
unable to create new native thread线程太多
Requested array size exceeds VM limit数组过大

6.2 排查 OOM 标准流程

① 确认 OOM 类型
    │
    ▼
② 保留现场
    │
    ▼
③ 导出 heap dump
    │
    ▼
④ 分析 dump 文件
    │
    ▼
⑤ 找到大对象或泄漏对象
    │
    ▼
⑥ 分析对象引用链
    │
    ▼
⑦ 定位具体代码
    │
    ▼
⑧ 修复代码或调整参数
    │
    ▼
⑨ 压测验证

6.3 生产建议 JVM 参数

JDK 9+:

-Xms4g -Xmx4g
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/
-XX:+ExitOnOutOfMemoryError
-XX:+UseG1GC
-Xlog:gc*:file=/data/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100M

JDK 8:

-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/data/logs/gc.log

6.4 常见 OOM 场景和解决方案

集合无限增长

问题代码:

List<Object> list = new ArrayList<>();
while (true) {
    list.add(new Object());
}

解决方案:

方案
限制集合大小
分页处理
批量处理后及时清理
使用队列削峰
避免全量加载

查询数据一次性加载太多

问题代码:

List<Order> orders = orderMapper.selectList(null);

如果订单有几百万条,可能直接打爆堆。

解决方式:

Page<Order> page = new Page<>(pageNo, pageSize);
orderMapper.selectPage(page, queryWrapper);

或者使用游标、分片、批处理。


线程池无界队列导致 OOM

问题代码:

Executors.newFixedThreadPool(10);

原因:底层使用无界 LinkedBlockingQueue,任务堆积后可能 OOM。

解决方式:

new ThreadPoolExecutor(
    16, 32,
    60, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1000),
    threadFactory,
    new ThreadPoolExecutor.CallerRunsPolicy()
);

Metaspace OOM

常见原因:

原因
动态生成类太多
CGLIB 代理过多
Groovy 动态脚本
频繁热部署
ClassLoader 泄漏

参数控制:

-XX:MaxMetaspaceSize=512m

注意:不能只加内存,还要排查类加载泄漏。


Direct Memory OOM

常见于:

场景
Netty
NIO
ByteBuffer.allocateDirect
文件传输
RocketMQ / Kafka 客户端

参数控制:

-XX:MaxDirectMemorySize=1g

同时要排查直接内存是否及时释放。


unable to create new native thread

常见原因:

原因
线程创建太多
线程池配置不合理
操作系统线程数限制
容器内存限制过小

解决方案:

方案
减少线程数
使用线程池复用
排查是否死循环创建线程
调整 Linux ulimit
检查容器资源限制

6.5 常用排查命令

操作命令
查看 Java 进程jps -l
查看 JVM 参数jcmd <pid> VM.flags
查看堆信息jmap -heap <pid>
导出 dumpjmap -dump:format=b,file=/tmp/heap.hprof <pid>
查看线程jstack <pid> > thread.txt
查看 GC 情况jstat -gcutil <pid> 1000 10

6.6 常用分析工具

工具作用
MAT分析 heap dump
VisualVM可视化分析 JVM
Arthas在线诊断
JProfiler商业性能分析
GCViewer分析 GC 日志

7. RocketMQ、RabbitMQ、Kafka 对比

7.1 一句话总结

MQ定位
RabbitMQ传统消息队列,功能丰富,路由灵活
RocketMQ金融级业务消息,事务消息、延迟消息强
Kafka高吞吐分布式日志平台,适合大数据和流处理

7.2 核心对比表

对比项RabbitMQRocketMQKafka
开发语言ErlangJavaScala / Java
核心模型Exchange + QueueTopic + QueueTopic + Partition
吞吐量中等很高
延迟较低较低
消息顺序支持支持较好分区内有序
延迟消息插件或 TTL + 死信原生支持不原生,需额外设计
事务消息支持但较弱原生强支持支持事务,偏流处理
消息堆积能力一般很强
路由能力很强一般一般
运维复杂度中等中等偏高较高
适合场景业务解耦、复杂路由订单、支付、交易日志、埋点、大数据、流处理

7.3 RabbitMQ

核心架构:

Producer ──► Exchange ──► Queue ──► Consumer

Exchange 类型:

类型说明
Direct精确匹配 routing key
Topic通配符匹配
Fanout广播
Headers根据 header 匹配
优点缺点
路由模型强大大量消息堆积时性能下降明显
延迟低吞吐不如 Kafka
功能成熟Erlang 技术栈对 Java 团队运维门槛较高
社区生态好
支持确认机制

适合场景:

场景
普通业务异步解耦
邮件发送
短信发送
订单状态通知
复杂路由场景

7.4 RocketMQ

核心架构:

Producer ──► NameServer ──► Broker ──► Consumer

核心概念:

概念说明
Topic消息主题
MessageQueueTopic 下的队列
Producer生产者
Consumer消费者
Broker消息存储节点
NameServer注册中心
优点缺点
支持事务消息生态不如 Kafka 广
支持延迟消息运维复杂度高于 RabbitMQ
支持顺序消息需理解 Broker、CommitLog、ConsumeQueue 等机制
消息堆积能力强
适合电商、支付、交易场景
Java 技术栈友好

适合场景:

场景
订单超时关闭
支付结果通知
交易状态流转
分布式事务最终一致性
延迟任务
顺序消费

7.5 Kafka

核心架构:

Producer ──► Topic ──► Partition ──► Consumer Group

Kafka 的核心是分区日志:

Partition 0:  msg1 → msg2 → msg3
Partition 1:  msg4 → msg5 → msg6
优点缺点
吞吐量极高单条消息路由能力不如 RabbitMQ
消息堆积能力极强延迟消息不是强项
非常适合日志和流处理业务事务消息不如 RocketMQ
分区扩展能力强运维复杂度较高
生态丰富(Flink、Spark、ClickHouse)

适合场景:

场景
日志采集
用户行为埋点
大数据实时计算
流式处理
数据同步
MySQL Binlog 消费链路

7.6 三者如何选择

场景推荐
普通业务解耦RabbitMQ
邮件、短信、通知RabbitMQ
订单、支付、库存RocketMQ
分布式事务最终一致性RocketMQ
延迟任务、顺序消息RocketMQ
日志、埋点、大数据Kafka
实时计算、流处理Kafka
Binlog 数据同步Kafka

8. 面试回答模板

8.1 JVM 组成

JVM 主要由类加载子系统、运行时数据区、执行引擎、本地方法接口和本地方法库组成。
其中运行时数据区分为线程共享和线程私有两部分。线程共享区域包括堆和方法区,线程私有区域包括虚拟机栈、本地方法栈和程序计数器。堆是 GC 的主要区域,方法区在 JDK 8 之后由元空间实现,虚拟机栈存储方法调用产生的栈帧。


8.2 JMM

JMM 是 Java 内存模型,不是 JVM 的堆、栈这些内存结构,而是 Java 针对多线程访问共享变量定义的一套规范。
它主要解决可见性、原子性和有序性问题。volatile 可以保证可见性和有序性,但不能保证复合操作的原子性;synchronized 可以保证原子性、可见性和有序性。JMM 还定义了 happens-before 规则,用于判断一个操作对另一个操作是否可见。


8.3 GC 算法

Java 判断对象是否可回收主要使用可达性分析算法,从 GC Roots 出发,没有引用链可达的对象就可以被回收。
常见垃圾回收算法有标记-清除、复制、标记-整理和分代收集。新生代对象存活率低,适合复制算法;老年代对象存活率高,适合标记-清除或标记-整理。服务端常用 G1,它通过 Region 分区管理堆,可以优先回收垃圾最多的区域,并支持可预测停顿时间。


8.4 线程池配置

线程池核心参数包括核心线程数、最大线程数、空闲线程存活时间、任务队列、线程工厂和拒绝策略。
线程池执行任务时,先创建核心线程;核心线程满了以后放入队列;队列满了再创建非核心线程;达到最大线程数后执行拒绝策略。生产环境不建议使用 Executors 默认线程池,因为可能存在无界队列或无限创建线程的问题。CPU 密集型任务线程数可以设置为 CPU 核心数或核心数加一,IO 密集型任务可以设置为 CPU 核心数的 2 到 4 倍,最终应结合压测和监控调整。


8.5 execute 和 submit

execute()Executor 接口的方法,只能提交 Runnable,没有返回值,也不能直接获取任务执行结果。
submit()ExecutorService 的方法,可以提交 Runnable,也可以提交 Callable,并且会返回 Future,可以通过 Future.get() 获取结果或捕获异常。
execute() 不支持直接提交 Callablesubmit() 支持 Runnable,提交 RunnableFuture.get() 默认返回 null,也可以通过 submit(Runnable task, T result) 指定固定返回值。


8.6 OOM 排查

OOM 需要先判断是哪类内存溢出,例如 Java heap spaceMetaspaceDirect buffer memoryunable to create new native thread
生产环境应开启 HeapDumpOnOutOfMemoryError 并保留 GC 日志。排查时先看错误类型,再分析 heap dump,找出占用内存最多的对象和引用链。常见原因包括集合无限增长、一次性查询大量数据、线程池无界队列堆积、动态类过多、直接内存未释放等。解决时不能只加内存,还要定位代码中的对象泄漏或资源释放问题。


8.7 MQ 对比

RabbitMQ、RocketMQ、Kafka 的定位不同。
RabbitMQ 路由能力强,适合普通业务异步解耦和复杂路由;RocketMQ 对交易场景支持更好,原生支持事务消息、延迟消息和顺序消息,适合订单、支付、库存等场景;Kafka 本质上是分布式日志系统,吞吐量和消息堆积能力非常强,适合日志采集、用户行为埋点、大数据和流式处理。实际选型时,普通业务通知可以选 RabbitMQ,电商交易链路可以选 RocketMQ,日志和大数据链路可以选 Kafka。


9. 最终记忆版

JVM
 ├── 类加载、运行时数据区、执行引擎、JNI、本地库
 ├── 运行时数据区
 │    ├── 线程共享:堆、方法区 / 元空间
 │    └── 线程私有:虚拟机栈、本地方法栈、程序计数器

JMM
 ├── Java 内存模型,解决多线程下可见性、原子性、有序性问题
 ├── volatile:保证可见性和有序性,不保证原子性
 └── synchronized:三者都能保证

GC
 ├── 判断对象是否回收:可达性分析
 ├── 算法:标记清除、复制、标记整理、分代收集
 ├── 新生代 → 复制算法,老年代 → 标记整理
 └── 常用回收器:G1、ZGC、Parallel、CMS

线程池
 ├── 核心参数:corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、handler
 ├── CPU 密集型:CPU 核心数 + 1
 ├── IO 密集型:CPU 核心数 × 2 ~ 4
 └── 生产必须用有界队列,避免 Executors 默认线程池

execute / submit
 ├── execute:只支持 Runnable,没有返回值
 ├── submit:支持 Runnable 和 Callable,返回 Future
 └── submit 的异常会封装到 Future,调用 get() 时抛出

OOM
 ├── 先看类型 → 保留现场 → 导出 dump → 分析引用链
 └── 常见原因:集合过大、全量查询、无界队列、线程过多、Metaspace 泄漏、DirectMemory 泄漏

MQ
 ├── RabbitMQ:路由强,适合业务解耦
 ├── RocketMQ:事务消息、延迟消息强,适合订单支付
 └── Kafka:吞吐高、堆积强,适合日志和大数据