Java 开发岗位核心面试八股文指南

2 阅读30分钟

Java 开发岗位核心面试八股文指南(2026 最新版)

本资料涵盖 Java 后端开发面试的核心知识点,适用于校招/社招准备。

最后更新: 2026 年 3 月 19 日


📑 目录

  1. Java 基础
  2. 集合框架
  3. 并发编程
  4. JVM
  5. Spring 框架
  6. MySQL 数据库
  7. Redis 缓存
  8. 分布式与微服务
  9. 系统设计
  10. 高频面试题速查

第一部分 Java 基础

1.1 面向对象三大特性

封装(Encapsulation)

  • 将数据和方法捆绑在类中
  • 隐藏实现细节,对外提供公共接口
  • 使用访问修饰符控制可见性(private、protected、public)

继承(Inheritance)

  • 子类继承父类的属性和方法
  • 支持代码复用
  • Java 只支持单继承,但可实现多个接口

多态(Polymorphism)

  • 同一操作作用于不同对象产生不同行为
  • 编译时多态:方法重载(Overload)
  • 运行时多态:方法重写(Override)+ 父类引用指向子类对象

多态实现条件

  1. 继承关系
  2. 方法重写
  3. 父类引用指向子类对象
// 多态示例
Animal animal = new Dog();  // 父类引用指向子类对象
animal.sound();  // 调用 Dog 类的 sound() 方法

1.2 重载 vs 重写

对比项重载 (Overload)重写 (Override)
位置同一类中子类对父类方法
方法名相同相同
参数列表必须不同必须相同
返回类型无要求相同或是其子类
访问修饰符无要求不能低于父类
异常无要求不能抛出更多异常

1.3 == 与 equals 区别

// == 比较
- 基本数据类型:比较值是否相等
- 引用数据类型:比较内存地址是否相同

// equals 方法
- 默认实现(Object 类):比较地址(同 ==)
- 重写后(如 String):比较内容是否相等

示例

String s1 = new String("hello");
String s2 = new String("hello");

s1 == s2;        // false,地址不同
s1.equals(s2);   // true,内容相同

1.4 hashCode 作用及与 equals 关系

hashCode 作用

  • 哈希算法生成的整数值
  • 提高集合(HashMap、HashSet)查找效率
  • 先根据 hashCode 定位存储区域,再调用 equals 比较

约定

  1. equals 相等的对象,hashCode 必须相等
  2. hashCode 相等的对象,equals 不一定相等(哈希冲突)

为什么重写 equals 必须重写 hashCode

// 不重写 hashCode 的问题
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);

set.add(p1);
set.contains(p2);  // 可能返回 false,因为 hashCode 不同

1.5 String、StringBuffer、StringBuilder

特性StringStringBufferStringBuilder
可变性不可变(final 字符数组)可变可变
线程安全安全安全(synchronized)不安全
性能低(每次创建新对象)
适用场景少量字符串操作多线程字符串拼接单线程大量拼接

String 为什么不可变

  1. 安全性:参数传递不会被修改
  2. 线程安全:天然支持并发
  3. 缓存优化:支持字符串常量池
  4. 哈希码缓存:适合做 HashMap 的 key

1.6 final 关键字用法

修饰对象含义
不能被继承(如 String 类)
方法不能被子类重写
变量值不能被修改(引用不可变,内容可变)
参数方法内不能重新赋值

示例

final List<String> list = new ArrayList<>();
list.add("hello");  // ✓ 允许,修改内容
list = new ArrayList<>();  // ✗ 不允许,修改引用

final int x = 10;
x = 20;  // ✗ 不允许

JVM 优化

  • final 方法可能被内联优化
  • final 常量编译时存入常量池

1.7 static 关键字用法

类型说明
静态变量类级别共享,所有实例共用一份
静态方法可直接通过类名调用,不能访问非静态成员
静态代码块类加载时执行一次,用于初始化
静态内部类不依赖外部类实例,可独立创建
静态导包import static java.lang.Math.*;

静态代码块执行顺序

class Parent {
    static { System.out.println("父类静态块"); }
}

class Child extends Parent {
    static { System.out.println("子类静态块"); }
}

// 执行 Child.class 时:
// 1. 父类静态块
// 2. 子类静态块

1.8 try-catch-finally

执行规则

  1. finally 始终执行(即使 try 有 return)
  2. finally 在 return 之前执行
  3. finally 中不要包含 return(会覆盖 try 的返回值)

示例

public int test() {
    try {
        return 1;
    } finally {
        return 2;  // 最终返回 2,不推荐这样写
    }
}

资源关闭最佳实践

// Java 7+ try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 自动关闭资源
} catch (IOException e) {
    e.printStackTrace();
}

1.9 反射机制

定义:运行时动态获取类的信息(属性、方法、构造器)并操作对象

获取 Class 对象的三种方式

// 1. Class.forName(最常用,会初始化类)
Class<?> clazz1 = Class.forName("com.example.Person");

// 2. 类.class(不会初始化)
Class<?> clazz2 = Person.class;

// 3. 对象.getClass()
Person p = new Person();
Class<?> clazz3 = p.getClass();

反射创建对象

Class<?> clazz = Class.forName("com.example.Person");

// 方式 1:newInstance(已废弃)
Person p1 = (Person) clazz.newInstance();

// 方式 2:获取构造器(推荐)
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Person p2 = (Person) constructor.newInstance("张三");

反射应用场景

  • JDBC 驱动加载
  • Spring IOC 容器
  • 动态代理(AOP)
  • 自定义注解处理
  • 通用框架开发

1.10 创建对象的 5 种方式

方式代码示例是否调用构造器
new 关键字new Person()
Class.newInstanceclazz.newInstance()
Constructor.newInstanceconstructor.newInstance()
clone 方法obj.clone()
反序列化ObjectInputStream.readObject()

1.11 深拷贝 vs 浅拷贝

类型说明实现方式
浅拷贝只复制对象本身,引用仍指向原对象实现 Cloneable 接口
深拷贝复制对象及其引用的所有对象序列化 + 反序列化

浅拷贝示例

class Person implements Cloneable {
    String name;
    Address address;  // 引用类型
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝,address 仍指向原对象
    }
}

深拷贝示例

// 通过序列化实现深拷贝
public Person deepClone() throws Exception {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return (Person) ois.readObject();
}

第二部分 集合框架

2.1 Collection 体系结构

Collection(接口)
├── List(有序、可重复)
│   ├── ArrayList(动态数组)
│   ├── LinkedList(双向链表)
│   └── Vector(线程安全,已过时)
│       └── Stack(栈)
├── Set(无序、不可重复)
│   ├── HashSet(哈希表)
│   │   └── LinkedHashSet(维护插入顺序)
│   └── TreeSet(红黑树,可排序)
└── Queue(队列)
    ├── PriorityQueue(优先队列)
    └── Deque(双端队列)
        ├── ArrayDeque
        └── LinkedList

2.2 ArrayList vs LinkedList

特性ArrayListLinkedList
底层结构动态数组双向链表
随机访问O(1),支持 get(i)O(n),需遍历
插入删除尾部 O(1),中间 O(n)头尾 O(1),中间需遍历
内存占用预留扩容空间额外存储前后节点指针
扩容机制自动扩容(1.5 倍)无需扩容
线程安全不安全不安全
适用场景读多写少频繁头尾插入删除

ArrayList 扩容机制

// 默认容量:10
// 扩容公式:newCapacity = oldCapacity + (oldCapacity >> 1)
// 即:新容量 = 原容量 * 1.5

// 扩容代码(简化版)
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.3 HashMap 核心原理(⭐⭐⭐⭐⭐)

底层结构

JDK 1.7:数组 + 链表(头插法) JDK 1.8:数组 + 链表/红黑树(尾插法)

HashMap 结构:
┌─────────┐
│   [0]   │ ──→ Node(key1, value1) → Node(key2, value2)
├─────────┤
│   [1]   │ ──→ null
├─────────┤
│   [2]   │ ──→ TreeNode(key3, value3) → TreeNode(key4, value4)
└─────────┘

核心参数

参数默认值说明
DEFAULT_INITIAL_CAPACITY16默认初始容量
MAXIMUM_CAPACITY1 << 30最大容量
DEFAULT_LOAD_FACTOR0.75f默认负载因子
TREEIFY_THRESHOLD8链表转红黑树阈值
UNTREEIFY_THRESHOLD6红黑树转链表阈值

关键方法原理

put 方法流程

public V put(K key, V value) {
    // 1. 计算 hash 值
    int hash = hash(key);
    int index = indexFor(hash, table.length);
    
    // 2. 判断桶是否为空
    if (table[index] == null) {
        table[index] = newNode(hash, key, value, null);
    } else {
        // 3. 遍历链表/红黑树
        for (Node<K,V> e = table[index]; e != null; e = e.next) {
            // 4. key 已存在,覆盖 value
            if (e.hash == hash && key.equals(e.key)) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }
        // 5. 头插法/尾插法插入新节点
        addNode(table[index], hash, key, value);
    }
    
    // 6. 判断是否需要扩容
    if (++size > threshold) {
        resize();
    }
    return null;
}

hash 方法(扰动函数):

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    // 高低 16 位异或,减少哈希冲突
}

扩容机制

// 扩容条件:size > capacity * loadFactor
// 扩容后:容量翻倍,重新 hash 分布

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int newCap = oldCap << 1;  // 容量翻倍
    
    // 重新 hash 分布
    // JDK 1.8 优化:无需重新计算 hash,根据高位判断位置
    for (int j = 0; j < oldCap; ++j) {
        Node<K,V> e = oldTab[j];
        if (e != null) {
            // 原位置 或 原位置 + oldCap
            if ((e.hash & oldCap) == 0) {
                newTab[j] = e;
            } else {
                newTab[j + oldCap] = e;
            }
        }
    }
}

为什么负载因子是 0.75

  • 时间效率与空间效率的平衡
  • 根据泊松分布,负载因子 0.75 时,链表长度超过 8 的概率极低(约 0.00000006)

为什么链表长度超过 8 转红黑树

  • 根据泊松分布统计,链表长度达到 8 的概率非常低
  • 红黑树查找 O(log n),链表查找 O(n)
  • 转换阈值 8,还原阈值 6(避免频繁转换)

2.4 ConcurrentHashMap 原理(⭐⭐⭐⭐⭐)

JDK 1.7 vs JDK 1.8

特性JDK 1.7JDK 1.8
数据结构分段数组(Segment[])数组 + 链表/红黑树
锁机制分段锁(ReentrantLock)CAS + synchronized
锁粒度Segment 级别桶(Node)级别
并发度Segment 数量数组长度

JDK 1.8 实现原理

ConcurrentHashMap 结构:
┌────────────────────────────────────────┐
│              Node[] table               │
├────────────────────────────────────────┤
│  [0] → Node → Node → TreeNode          │
│  [1] → Node → Node                     │
│  [2] → null                             │
│  ...                                    │
└────────────────────────────────────────┘

锁粒度:每个桶(数组元素)独立加锁

put 方法流程

public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 1. 计算 hash
    int hash = spread(key.hashCode());
    int binCount = 0;
    
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        
        // 2. 桶为空,CAS 插入
        if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<>(hash, key, value)))
                break;
        }
        // 3. 正在扩容,帮助扩容
        else if ((fh = f.hash) == MOVED)
            helpTransfer(tab, f);
        else {
            // 4. 桶不为空,synchronized 锁头节点
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        // 链表遍历
                        for (Node<K,V> e = f;; ++binCount) {
                            if (e.hash == hash && key.equals(e.key)) {
                                // key 存在,覆盖
                                oldVal = e.val;
                                e.val = value;
                                break;
                            }
                            if (e.next == null) {
                                e.next = new Node<>(hash, key, value, null);
                                break;
                            }
                        }
                    } else if (f instanceof TreeBin) {
                        // 红黑树插入
                        Node<K,V> p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value);
                        if (p != null) {
                            oldVal = p.val;
                            p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 5. 判断是否转红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 6. 更新 size
    addCount(1L, binCount);
    return null;
}

如何保证线程安全

  1. CAS 操作:无锁插入空桶
  2. synchronized:锁链表/红黑树头节点
  3. volatile:保证可见性
  4. 分段思想:不同桶可并发操作

2.5 HashMap vs HashTable

特性HashMapHashTable
父类AbstractMapDictionary
null 支持key 可为 null(1 个),value 可为 nullkey/value 都不能为 null
线程安全不安全安全(synchronized)
效率
迭代器fail-fastfail-fast
推荐替代-ConcurrentHashMap

2.6 Collection vs Collections

名称类型说明
Collection接口集合体系的顶层接口(Set、List 的父接口)
Collections工具类提供集合操作的静态方法

Collections 常用方法

// 排序
Collections.sort(list);

// 二分查找
Collections.binarySearch(list, key);

// 反转
Collections.reverse(list);

// 线程安全化
Collections.synchronizedList(list);

// 空列表
Collections.emptyList();

第三部分 并发编程

3.1 线程与进程

概念说明
程序静态的指令和数据文件
进程程序的一次执行过程,资源分配的基本单位
线程进程的更小执行单位,CPU 调度的基本单位

关系

  • 一个进程可包含多个线程
  • 线程共享进程的堆和方法区
  • 每个线程有独立的程序计数器、虚拟机栈、本地方法栈

3.2 线程创建方式

方式 1:继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

new MyThread().start();

方式 2:实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

new Thread(new MyRunnable()).start();

方式 3:实现 Callable 接口(有返回值)

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 100;
    }
}

FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
Integer result = task.get();  // 获取返回值

方式 4:线程池(推荐)

ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("线程执行"));
executor.shutdown();

3.3 线程状态

NEW → RUNNABLE ↔ WAITING/TIMED_WAITING → BLOCKED → TERMINATED

NEW:新建,未启动
RUNNABLE:可运行(包括就绪和运行中)
BLOCKED:等待获取锁
WAITING:无限期等待(wait/join/park)
TIMED_WAITING:限期等待(sleep/wait/ park)
TERMINATED:终止

状态转换

// NEW → RUNNABLE
thread.start();

// RUNNABLE → BLOCKED
synchronized 锁竞争失败

// RUNNABLE → WAITING
object.wait();
thread.join();
LockSupport.park();

// RUNNABLE → TIMED_WAITING
Thread.sleep(1000);
object.wait(1000);
LockSupport.parkNanos(1000);

// WAITING/BLOCKED → RUNNABLE
object.notify()/notifyAll();
获取到锁
LockSupport.unpark(thread);

// RUNNABLE → TERMINATED
run() 方法执行完成

3.4 并发编程三要素

原子性(Atomicity)

  • 操作要么全部执行,要么都不执行
  • 保证:synchronized、Lock、原子类

可见性(Visibility)

  • 一个线程修改共享变量,其他线程立即可见
  • 保证:volatile、synchronized、Lock

有序性(Ordering)

  • 程序执行顺序符合预期
  • 保证:volatile(禁止指令重排)、happens-before 原则

3.5 volatile 关键字

作用

  1. 保证可见性
  2. 禁止指令重排
  3. 不保证原子性

内存语义

  • 写 volatile 变量:刷新到主内存
  • 读 volatile 变量:从主内存读取

使用场景

// 状态标记
private volatile boolean running = true;

// 双重检查锁(DCL)单例
private static volatile Singleton instance;

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();  // volatile 防止重排
            }
        }
    }
    return instance;
}

3.6 synchronized 原理

锁升级过程(JDK 1.6+):

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
锁状态适用场景实现方式
无锁无竞争无同步
偏向锁单线程访问偏向第一个获取锁的线程
轻量级锁轻度竞争CAS 自旋
重量级锁激烈竞争操作系统互斥量

使用方式

// 修饰实例方法(锁当前对象)
public synchronized void method() {}

// 修饰静态方法(锁 Class 对象)
public static synchronized void method() {}

// 修饰代码块(锁指定对象)
public void method() {
    synchronized (this) {
        // 临界区
    }
}

3.7 ReentrantLock vs synchronized

特性synchronizedReentrantLock
实现层面JVM 层面API 层面
锁释放自动释放手动释放(finally 中)
等待可中断是(lockInterruptibly)
公平锁支持(构造参数指定)
多条件等待单一条件(wait/notify)多个 Condition
性能JDK 1.6+ 优化后相当高竞争下略优

ReentrantLock 使用

ReentrantLock lock = new ReentrantLock(true);  // 公平锁

lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();  // 必须手动释放
}

// 可中断等待
lock.lockInterruptibly();

// 尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获取锁成功
    } finally {
        lock.unlock();
    }
}

3.8 线程池核心参数(⭐⭐⭐⭐⭐)

ThreadPoolExecutor 构造方法

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

核心参数详解

参数说明推荐配置
corePoolSize核心线程数,线程池维持的最小线程数CPU 密集型:CPU 核心数 +1
IO 密集型:2*CPU 核心数
maximumPoolSize最大线程数根据任务类型调整
keepAliveTime非核心线程空闲超时时间默认 60 秒
workQueue任务队列ArrayBlockingQueue(有界)
LinkedBlockingQueue(无界)
threadFactory线程创建工厂自定义线程名,便于排查
handler拒绝策略根据业务需求选择

工作流程

新任务提交
    ↓
核心线程数未满 → 创建核心线程执行
    ↓
核心线程已满 → 加入工作队列
    ↓
队列已满 → 创建非核心线程
    ↓
达到最大线程数 → 执行拒绝策略

拒绝策略

策略说明
AbortPolicy抛出 RejectedExecutionException(默认)
CallerRunsPolicy调用者线程执行
DiscardPolicy直接丢弃
DiscardOldestPolicy丢弃最老任务,执行新任务

线程池使用示例

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    8,                              // 核心线程数
    16,                             // 最大线程数
    60,                             // 空闲存活时间
    TimeUnit.SECONDS,               // 时间单位
    new ArrayBlockingQueue<>(100),  // 有界队列
    new ThreadFactoryBuilder()      // 自定义线程工厂
        .setNameFormat("pool-%d")
        .build(),
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);

executor.submit(() -> System.out.println("任务执行"));
executor.shutdown();

3.9 ThreadLocal 原理

作用:线程本地变量,每个线程有独立的变量副本

应用场景

  • 数据库连接管理
  • Session 管理
  • SimpleDateFormat 线程安全

原理

// 每个 Thread 内部维护 ThreadLocalMap
class Thread {
    ThreadLocalMap threadLocals;
}

// ThreadLocalMap 存储键值对
// key:ThreadLocal 对象(弱引用)
// value:线程本地变量值

内存泄漏问题

  • key 是弱引用,value 是强引用
  • ThreadLocal 被回收后,value 仍无法回收
  • 解决:使用完调用 remove() 方法
private static final ThreadLocal<SimpleDateFormat> formatter = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

try {
    String date = formatter.get().format(new Date());
} finally {
    formatter.remove();  // 必须清理
}

3.10 AQS(AbstractQueuedSynchronizer)

核心思想

  • 使用 volatile int state 表示同步状态
  • 使用 CLH 队列管理等待线程

核心方法

// 获取同步状态
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }

// 释放同步状态
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }

// 等待队列
private transient volatile Node head;
private transient volatile Node tail;

基于 AQS 实现的锁

  • ReentrantLock
  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • ReentrantReadWriteLock

3.11 并发工具类

CountDownLatch(倒计时门闩)

CountDownLatch latch = new CountDownLatch(3);

// 子线程
new Thread(() -> {
    // 执行任务
    latch.countDown();  // 计数减 1
}).start();

// 主线程等待
latch.await();  // 等待计数为 0
System.out.println("所有任务完成");

CyclicBarrier(循环屏障)

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障");
});

// 线程中
barrier.await();  // 等待其他线程

Semaphore(信号量)

Semaphore semaphore = new Semaphore(5);  // 5 个许可

semaphore.acquire();  // 获取许可
try {
    // 执行任务
} finally {
    semaphore.release();  // 释放许可
}

第四部分 JVM

4.1 JVM 内存区域(⭐⭐⭐⭐⭐)

┌─────────────────────────────────────────────────────┐
│                     JVM 内存结构                      │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌──────────────────────────────────────────────┐   │
│  │              堆(Heap)                       │   │
│  │  - 对象实例                                     │   │
│  │  - 垃圾回收主要区域                             │   │
│  │  - 新生代(Eden + Survivor)                   │   │
│  │  - 老年代                                       │   │
│  └──────────────────────────────────────────────┘   │
│                                                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │ 方法区       │  │ 虚拟机栈     │  │ 本地方法栈   │  │
│  │ (元空间)     │  │             │  │             │  │
│  │ - 类信息    │  │ - 栈帧      │  │ - Native    │  │
│  │ - 常量池    │  │ - 局部变量  │  │   方法      │  │
│  │ - 静态变量  │  │ - 操作数栈  │  │             │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  │
│                                                      │
│  ┌──────────────────────────────────────────────┐   │
│  │              程序计数器 (PC Register)          │   │
│  │  - 记录当前执行的字节码指令地址                 │   │
│  └──────────────────────────────────────────────┘   │
│                                                      │
└─────────────────────────────────────────────────────┘

各区域详解

区域作用线程共享OOM 异常
存储对象实例共享
方法区类信息、常量、静态变量共享
虚拟机栈Java 方法执行(栈帧)私有是(StackOverflowError)
本地方法栈Native 方法执行私有
程序计数器记录字节码执行位置私有

4.2 堆内存结构

堆内存(Heap)
├── 新生代(Young Generation)
│   ├── Eden 区(8/10)
│   └── Survivor 区(2/10)
│       ├── From
│       └── To
└── 老年代(Old Generation)

对象分配流程

  1. 新对象优先分配在 Eden 区
  2. Eden 满时触发 Minor GC
  3. 存活对象复制到 Survivor 区
  4. 年龄达到阈值(默认 15)晋升老年代
  5. 大对象直接进入老年代

4.3 垃圾回收算法

算法原理优点缺点适用区域
标记 - 清除标记可回收对象,统一清除简单内存碎片老年代
复制存活对象复制到另一区域无碎片空间利用率低新生代
标记 - 整理标记后向一端整理无碎片效率低老年代
分代收集根据对象生命周期分代高效复杂全堆

4.4 垃圾收集器

收集器特点适用场景
Serial单线程,STW客户端模式
ParNewSerial 多线程版新生代
Parallel Scavenge关注吞吐量后台计算
CMS低延迟,标记清除Web 应用
G1分区回收,可预测停顿大堆内存
ZGC超低停顿(<10ms)JDK 11+,大堆

G1 收集器特点

  • 将堆划分为多个 Region
  • 可预测停顿时间
  • 整体标记整理,局部复制

4.5 类加载机制

类加载过程

加载 → 验证 → 准备 → 解析 → 初始化
阶段说明
加载读取字节码文件,生成 Class 对象
验证验证字节码合法性
准备为静态变量分配内存并设默认值
解析符号引用转为直接引用
初始化执行静态代码块和静态变量赋值

双亲委派模型

                    Bootstrap ClassLoader
                           ↑
                    Extension ClassLoader
                           ↑
                    Application ClassLoader
                           ↑
                    自定义 ClassLoader

委派过程

  1. 收到类加载请求
  2. 先委托给父加载器
  3. 父加载器无法加载,子加载器才尝试

优点

  • 保证核心类安全(防止篡改)
  • 避免重复加载

破坏双亲委派

  • SPI 机制(JDBC、JNDI)
  • 热部署(OSGi)
  • 代码热替换

4.6 JVM 调优参数

常用参数

参数说明
-Xms初始堆大小
-Xmx最大堆大小
-Xmn新生代大小
-XX:MetaspaceSize元空间初始大小
-XX:MaxMetaspaceSize元空间最大大小
-XX:+UseG1GC使用 G1 收集器
-XX:MaxGCPauseMillis最大 GC 停顿时间
-XX:+PrintGCDetails打印 GC 日志
-Xloggc:/path/gc.logGC 日志路径

调优步骤

  1. 监控 GC 日志
  2. 分析工具:gceasy.io、GCViewer
  3. 调整堆大小和收集器
  4. 压测验证

第五部分 Spring 框架

5.1 Spring IOC(控制反转)

核心概念

  • 将对象创建权交给 Spring 容器
  • 通过依赖注入(DI)实现

注入方式

// 构造器注入(推荐)
@Component
public class UserService {
    private final UserRepository repo;
    
    @Autowired
    public UserService(UserRepository repo) {
        this.repo = repo;
    }
}

// Setter 注入
@Component
public class UserService {
    private UserRepository repo;
    
    @Autowired
    public void setRepo(UserRepository repo) {
        this.repo = repo;
    }
}

// 字段注入(不推荐)
@Component
public class UserService {
    @Autowired
    private UserRepository repo;
}

常用注解

注解说明
@Component通用组件
@Service服务层
@Repository数据访问层
@Controller控制层
@Autowired按类型注入
@Resource按名称注入
@Qualifier配合@Autowired 按名称

5.2 Spring AOP(面向切面编程)

核心概念

  • 切面(Aspect):横切关注点的模块化
  • 连接点(Joinpoint):可插入切面的点
  • 通知(Advice):切面在连接点的动作
  • 切入点(Pointcut):匹配连接点的表达式
  • 织入(Weave):将切面应用到目标对象

通知类型

@Aspect
@Component
public class LogAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint jp) {
        // 前置通知
    }
    
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturning(Object result) {
        // 返回后通知
    }
    
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "e")
    public void afterThrowing(Exception e) {
        // 异常通知
    }
    
    @After("execution(* com.example.service.*.*(..))")
    public void after() {
        // 最终通知
    }
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 环绕通知
        return pjp.proceed();
    }
}

应用场景

  • 日志记录
  • 事务管理
  • 权限控制
  • 性能监控

5.3 Spring 事务管理

事务传播行为

传播行为说明
REQUIRED加入当前事务,无则新建(默认)
REQUIRES_NEW总是新建事务,挂起当前事务
NESTED嵌套事务
SUPPORTS支持当前事务,无则以非事务执行
NOT_SUPPORTED以非事务执行,挂起当前事务
MANDATORY必须存在事务,否则抛异常
NEVER必须非事务,否则抛异常

事务隔离级别

级别说明问题
DEFAULT使用数据库默认-
READ_UNCOMMITTED读未提交脏读、不可重复读、幻读
READ_COMMITTED读已提交不可重复读、幻读
REPEATABLE_READ可重复读幻读
SERIALIZABLE串行化

事务失效场景

  1. 方法非 public
  2. 同类内部调用(自调用)
  3. 异常被捕获未抛出
  4. 数据库不支持事务
  5. 异常类型不匹配

5.4 Spring Bean 生命周期

实例化 → 属性赋值 → 初始化 → 使用 → 销毁

详细流程

  1. 实例化 Bean
  2. 属性注入
  3. 调用 Aware 接口方法
  4. BeanPostProcessor 前置处理
  5. 调用初始化方法(@PostConstruct、InitializingBean)
  6. BeanPostProcessor 后置处理
  7. Bean 就绪,可使用
  8. 容器关闭时调用销毁方法(@PreDestroy、DisposableBean)

5.5 Spring Boot 自动装配

核心注解

@SpringBootApplication
= @SpringBootConfiguration
+ @EnableAutoConfiguration
+ @ComponentScan

自动装配原理

  1. @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
  2. 读取 META-INF/spring.factories 中的自动配置类
  3. 根据条件注解(@Conditional)判断是否生效
  4. 加载符合条件的配置类

自定义 Starter

@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }
}

第六部分 MySQL 数据库

6.1 MySQL 架构

┌─────────────────────────────────────────────────────┐
│                  MySQL 架构                          │
├─────────────────────────────────────────────────────┤
│  连接层:连接管理、权限验证                           │
│      ↓                                              │
│  服务层:SQL 解析、优化、缓存                        │
│      ↓                                              │
│  引擎层:存储引擎(InnoDB、MyISAM)                  │
│      ↓                                              │
│  存储层:数据存储、索引                              │
└─────────────────────────────────────────────────────┘

6.2 存储引擎对比

特性InnoDBMyISAM
事务支持不支持
锁粒度行锁表锁
外键支持不支持
崩溃恢复支持不支持
COUNT(*)全表扫描内置计数器
适用场景事务、高并发读多写少

6.3 索引原理(⭐⭐⭐⭐⭐)

B+ 树结构

        ┌─────────┐
        │  17,35  │  非叶子节点(只存索引)
        └────┬────┘
             │
    ┌────────┼────────┐
    ↓        ↓        ↓
┌───────┐ ┌───────┐ ┌───────┐
│1,5,10 │ │20,25,30│ │40,45,50│  叶子节点(存数据)
└───────┘ └───────┘ └───────┘
    ↑        ↑        ↑
  数据行   数据行   数据行

B+ 树特点

  • 非叶子节点只存索引,不存数据
  • 叶子节点存所有数据,且有指针相连
  • 查询效率稳定,O(log n)

聚簇索引 vs 非聚簇索引

类型说明InnoDB
聚簇索引数据与索引在一起主键索引
非聚簇索引索引与数据分离二级索引

回表查询

  • 二级索引查到主键
  • 再用主键查聚簇索引获取完整数据

覆盖索引

  • 查询的列都在索引中,无需回表

6.4 索引优化原则

最左前缀原则

-- 联合索引 (a, b, c)
WHERE a = 1 AND b = 2 AND c = 3  -- ✓ 使用索引
WHERE a = 1 AND b = 2            -- ✓ 使用索引
WHERE a = 1                      -- ✓ 使用索引
WHERE b = 2 AND c = 3            -- ✗ 不使用索引
WHERE a = 1 AND c = 3            -- △ 只使用 a 列索引

索引失效场景

场景示例说明
函数操作WHERE DATE(create_time) = '2024-01-01'索引失效
类型转换WHERE phone = 13800000000(phone 是字符串)隐式转换,索引失效
模糊查询WHERE name LIKE '%张%'前缀通配符,索引失效
OR 条件WHERE a = 1 OR b = 2b 无索引则全表扫描
NOT/!=WHERE age != 18可能全表扫描

索引优化建议

  1. 为高频查询字段创建索引
  2. 使用覆盖索引减少回表
  3. 避免 SELECT *
  4. 大表分页优化:WHERE id > 1000 LIMIT 10
  5. 定期分析慢查询日志

6.5 事务隔离级别

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ(MySQL 默认)✗*
SERIALIZABLE

*MySQL 通过 MVCC 和 Next-Key Lock 解决幻读


6.6 MVCC(多版本并发控制)

实现原理

  • 每行数据有隐藏列(事务 ID、回滚指针)
  • 读操作读取历史版本
  • 写操作创建新版本

Read View

  • 当前活跃事务列表
  • 最小活跃事务 ID
  • 最大事务 ID

可见性判断

事务 ID < 最小活跃 ID → 可见
事务 ID >= 最大活跃 ID → 不可见
最小 <= 事务 ID < 最大 → 检查是否在活跃列表

6.7 锁机制

行锁类型

锁类型说明
记录锁锁住索引记录
间隙锁锁住索引间隙,防止幻读
临键锁记录锁 + 间隙锁

死锁排查

-- 查看死锁日志
SHOW ENGINE INNODB STATUS;

-- 查看锁等待
SELECT * FROM information_schema.innodb_lock_waits;

6.8 SQL 优化

EXPLAIN 分析

EXPLAIN SELECT * FROM users WHERE id = 1;
字段说明
type访问类型(ALL < index < range < ref < eq_ref < const < system)
key实际使用的索引
rows扫描行数
Extra额外信息(Using index 覆盖索引,Using filesort 文件排序)

优化案例

-- 优化前:全表扫描
SELECT * FROM orders WHERE YEAR(create_time) = 2024;

-- 优化后:使用索引
SELECT * FROM orders 
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

-- 分页优化
SELECT * FROM users LIMIT 1000000, 10;  -- 慢

SELECT * FROM users WHERE id > 1000000 LIMIT 10;  -- 快

第七部分 Redis 缓存

7.1 数据结构与应用场景

数据结构应用场景
String缓存、计数器、分布式锁
Hash对象存储、购物车
List消息队列、最新列表
Set去重、好友关系
ZSet排行榜、优先级队列
Bitmap签到、状态标记
HyperLogLogUV 统计
Geo地理位置

7.2 持久化机制

RDB(Redis Database)

优点缺点
紧凑的二进制文件可能丢失最后一次快照后的数据
恢复速度快无法实时持久化
适合备份大数据量时 fork 耗时

触发方式

  • 手动:SAVEBGSAVE
  • 自动:满足配置条件(save 900 1、save 300 10、save 60 10000)

AOF(Append Only File)

优点缺点
数据更安全文件体积大
可配置每秒 fsync恢复速度慢
重写压缩-

重写机制

  • 合并多条命令
  • 忽略无效命令(如已删除的 key)

推荐配置

# RDB + AOF 混合持久化
save 900 1
save 300 10
save 60 10000

appendonly yes
appendfsync everysec

7.3 缓存问题及解决方案(⭐⭐⭐⭐⭐)

缓存穿透

问题:查询不存在的数据,请求直达数据库

解决方案

  1. 布隆过滤器:拦截不存在的 key
  2. 缓存空值:设置较短过期时间
  3. 接口层校验:参数合法性检查
// 布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("bloomFilter");
bloomFilter.tryInit(1000000L, 0.03);  // 预期 100 万,误差率 3%

if (!bloomFilter.contains(key)) {
    return null;  // 拦截
}

缓存击穿

问题:热点 key 过期,大量请求直达数据库

解决方案

  1. 互斥锁:只让一个线程查数据库
  2. 永不过期:逻辑过期,异步更新
// 互斥锁方案
public String getData(String key) {
    String value = redis.get(key);
    if (value == null) {
        if (tryLock("lock:" + key)) {
            try {
                value = redis.get(key);
                if (value == null) {
                    value = fetchFromDB();
                    redis.setex(key, 3600, value);
                }
            } finally {
                unlock("lock:" + key);
            }
        } else {
            Thread.sleep(50);
            return getData(key);  // 重试
        }
    }
    return value;
}

缓存雪崩

问题:大量 key 同时过期,请求直达数据库

解决方案

  1. 随机过期时间:基础时间 + 随机值
  2. 高可用集群:多节点分散压力
  3. 限流降级:保护数据库
// 随机过期时间
int expireTime = 3600 + new Random().nextInt(300);  // 3600~3900 秒
redis.setex(key, expireTime, value);

7.4 分布式锁

Redis 实现

// 加锁
String result = redis.set(lockKey, requestId, "NX", "PX", 30000);
if ("OK".equals(result)) {
    try {
        // 业务逻辑
    } finally {
        // 解锁(Lua 脚本)
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    }
}

Redisson 实现(推荐)

RLock lock = redisson.getLock("lockKey");
lock.lock();  // 自动续期(看门狗)
try {
    // 业务逻辑
} finally {
    lock.unlock();
}

// 尝试加锁
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

看门狗机制

  • 默认 30 秒过期
  • 每 10 秒自动续期
  • 解锁后停止续期

7.5 缓存一致性

方案对比

方案说明适用场景
先删缓存再更新 DB可能读到旧数据不推荐
先更新 DB 再删缓存Cache Aside Pattern推荐
延迟双删删→更新→sleep→删高一致性要求
监听 BinlogCanal 同步删除最终一致性

Cache Aside Pattern

// 读
public Data read(String key) {
    Data data = redis.get(key);
    if (data == null) {
        data = db.query(key);
        redis.setex(key, 3600, data);
    }
    return data;
}

// 写
public void write(String key, Data data) {
    db.update(key, data);
    redis.del(key);  // 删除缓存而非更新
}

7.6 Redis 集群

主从复制

  • 一主多从
  • 读写分离
  • 数据备份

哨兵模式

  • 监控主从节点
  • 自动故障转移
  • 高可用

Redis Cluster

  • 分片存储(16384 个 slot)
  • 去中心化
  • 自动故障转移

slot 分配

CRC16(key) % 16384 = slot 编号

第八部分 分布式与微服务

8.1 分布式事务

解决方案对比

方案特点适用场景
2PC强一致性,性能差不推荐
TCCTry-Confirm-Cancel,补偿机制高一致性要求
本地消息表最终一致性,简单可靠推荐
RocketMQ 事务消息半消息机制推荐
SeataAT 模式,无侵入推荐

Seata AT 模式原理

  1. 一阶段:执行 SQL,记录 undo_log
  2. 提交:释放本地锁
  3. 回滚:根据 undo_log 反向补偿

8.2 分布式 ID

生成方案

方案优点缺点
UUID简单无序,不适合索引
数据库自增有序单点,性能低
号段模式性能高依赖 DB
雪花算法有序,高性能时钟回拨问题

雪花算法结构

64 位 ID = 符号位 (1) + 时间戳 (41) + 机器 ID (10) + 序列号 (12)

8.3 微服务组件

组件功能常见实现
注册中心服务注册与发现Nacos、Eureka、Consul
配置中心配置管理Nacos、Apollo
网关路由、鉴权、限流Gateway、Zuul
负载均衡流量分发Ribbon、LoadBalancer
熔断降级故障隔离Sentinel、Hystrix
链路追踪调用链监控SkyWalking、Zipkin

8.4 限流算法

算法原理实现
计数器单位时间计数简单,临界问题
滑动窗口细分时间片平滑限流
漏桶固定速率流出平滑,无法应对突发
令牌桶固定速率放入令牌可应对突发流量

Sentinel 限流

@SentinelResource(value = "getUser", blockHandler = "blockHandler")
public User getUser(String id) {
    return userService.getUser(id);
}

public User blockHandler(String id, BlockException e) {
    return new User("限流中...");
}

第九部分 系统设计

9.1 高并发设计

核心思路

分流 → 缓存 → 异步 → 降级 → 限流

分层设计

层级策略
接入层DNS 轮询、CDN、负载均衡
网关层路由、鉴权、限流
服务层无状态、水平扩展
缓存层多级缓存、热点隔离
数据层分库分表、读写分离

9.2 秒杀系统设计

核心挑战

  • 瞬时高并发
  • 超卖问题
  • 黄牛刷单

解决方案

┌─────────────────────────────────────────────────────┐
│                  秒杀系统架构                        │
├─────────────────────────────────────────────────────┤
│                                                      │
│  用户 → CDN 静态资源                                 │
│       ↓                                              │
│  网关层:限流、黑名单、验证码                        │
│       ↓                                              │
│  Redis 预减库存(原子操作)                          │
│       ↓                                              │
│  MQ 异步下单(削峰)                                 │
│       ↓                                              │
│  数据库扣减库存(乐观锁)                            │
│                                                      │
└─────────────────────────────────────────────────────┘

关键代码

// Redis 预减库存
String stockKey = "seckill:stock:" + productId;
Long stock = redis.decr(stockKey);
if (stock < 0) {
    redis.incr(stockKey);
    return Result.error("库存不足");
}

// MQ 异步下单
seckillQueue.send(order);

// 数据库乐观锁
UPDATE stock SET count = count - 1 
WHERE product_id = ? AND count > 0;

9.3 短链接系统设计

核心流程

  1. 生成短码(MD5/雪花算法/Base62)
  2. 建立映射关系(短码 → 原 URL)
  3. 重定向(301/302)

短码生成

// Base62 编码
String shortCode = Base62.encode(snowflakeId());

// MD5 截取
String md5 = MD5(url + salt);
String shortCode = md5.substring(0, 8);

第十部分 高频面试题速查

10.1 Java 基础

问题关键词
String 为什么不可变final 字符数组、安全性、缓存
== 与 equals 区别地址 vs 内容
hashCode 与 equals约定、集合查找
final、finally、finalize修饰符、异常、GC
重载与重写同类 vs 父子类
深拷贝与浅拷贝引用是否复制
反射原理及应用Class 对象、动态代理
创建对象的方式new、反射、clone、序列化

10.2 集合框架

问题关键词
ArrayList vs LinkedList数组 vs 链表、随机访问
HashMap 底层原理数组 + 链表/红黑树、扩容
HashMap 线程安全吗不安全、ConcurrentHashMap
ConcurrentHashMap 原理CAS + synchronized、分段
HashMap vs HashTablenull 支持、线程安全
ArrayList 扩容机制1.5 倍、copy
HashMap 为什么用红黑树O(log n)、阈值 8

10.3 并发编程

问题关键词
线程创建方式Thread、Runnable、Callable、线程池
线程状态及转换NEW、RUNNABLE、BLOCKED、WAITING
volatile 原理可见性、禁止重排、不保证原子性
synchronized 原理锁升级、Monitor
ReentrantLock vs synchronizedAPI vs JVM、公平锁
线程池参数及流程corePoolSize、workQueue、拒绝策略
ThreadLocal 原理及泄漏线程本地变量、弱引用、remove
AQS 原理state、CLH 队列
CAS 原理及 ABA 问题乐观锁、版本号

10.4 JVM

问题关键词
JVM 内存区域堆、栈、方法区、程序计数器
垃圾回收算法标记清除、复制、标记整理
垃圾收集器CMS、G1、ZGC
类加载机制双亲委派、破坏场景
JVM 调优参数-Xms、-Xmx、G1GC
OOM 排查heap dump、MAT
对象分配过程Eden、Survivor、老年代

10.5 Spring

问题关键词
IOC 原理控制反转、依赖注入
AOP 原理及应用动态代理、切面、通知
Bean 生命周期实例化、初始化、销毁
事务传播行为REQUIRED、REQUIRES_NEW
事务隔离级别脏读、不可重复读、幻读
Spring Boot 自动装配spring.factories、条件注解
循环依赖解决三级缓存

10.6 MySQL

问题关键词
索引底层结构B+ 树、聚簇索引
索引优化原则最左前缀、覆盖索引
索引失效场景函数、类型转换、模糊查询
事务隔离级别READ COMMITTED、REPEATABLE READ
MVCC 原理隐藏列、Read View
锁机制行锁、间隙锁、临键锁
SQL 优化EXPLAIN、慢查询
分库分表水平拆分、垂直拆分

10.7 Redis

问题关键词
数据结构及应用String、Hash、ZSet
持久化机制RDB、AOF
缓存穿透/击穿/雪崩布隆过滤器、互斥锁、随机过期
分布式锁实现setnx、Redisson、看门狗
缓存一致性Cache Aside、延迟双删
Redis 集群主从、哨兵、Cluster
热点 key 处理本地缓存、拆分

10.8 分布式

问题关键词
分布式事务方案TCC、本地消息表、Seata
分布式 ID 生成雪花算法、号段模式
限流算法令牌桶、漏桶、滑动窗口
微服务组件注册中心、配置中心、网关
CAP 理论一致性、可用性、分区容错
BASE 理论基本可用、软状态、最终一致

附录

A. 复习优先级

第一梯队(必会):
✅ HashMap/ConcurrentHashMap
✅ 线程池
✅ synchronized/volatile
✅ MySQL 索引
✅ Redis 缓存问题
✅ Spring 事务

第二梯队(高频):
✅ JVM 内存模型
✅ 垃圾回收
✅ Spring IOC/AOP
✅ 分布式锁
✅ 分布式事务

第三梯队(加分):
✅ JVM 调优
✅ 系统设计
✅ 高并发方案

B. 面试技巧

  1. STAR 法则:Situation、Task、Action、Result
  2. 由浅入深:先说概念,再讲原理,最后举例
  3. 引导话题:主动提及自己熟悉的领域
  4. 诚实原则:不会的承认,表示愿意学习

C. 推荐资源

  • JavaGuide: javaguide.cn
  • 小林 coding: 图解计算机基础
  • 极客时间: Java 并发、JVM 实战
  • LeetCode: 算法刷题

文档版本:2026.03
最后更新:2026 年 3 月 19 日
祝面试顺利!🎉