一、Java 基础
1. JVM 内存模型
问题:请解释 JVM 的内存区域划分及其作用?
详解:
JVM 内存主要分为以下几个区域:
-
堆(Heap):存储对象实例和数组,是垃圾回收的主要区域
-
方法区(Method Area):存储类信息、常量、静态变量等(JDK8 后称为元空间 MetaSpace)
-
虚拟机栈(VM Stack):存储方法调用的栈帧,包含局部变量表、操作数栈等
-
本地方法栈(Native Stack):为 Native 方法服务
-
程序计数器(PC Register):记录当前线程执行的字节码行号
示例:
public class MemoryModel {
private static int staticVar = 1; // 方法区
private int instanceVar = 2; // 堆
public void method() {
int localVar = 3; // 虚拟机栈
Object obj = new Object(); // 堆
}
}
2. HashMap 原理
问题:HashMap 的底层实现原理是什么?JDK1.8 做了哪些优化?
详解:
-
数组+链表+红黑树结构(JDK1.8 引入红黑树)
-
默认初始容量16,负载因子0.75
-
当链表长度>8且数组长度>64时,链表转为红黑树
-
哈希冲突解决:拉链法
-
JDK1.8 优化:
-
链表插入方式改为尾插法(解决并发扩容死循环问题)
-
引入红黑树提高查询效率
-
示例:
Map<String, Integer> map = new HashMap<>();
map.put("key", 1); // 计算hash,定位桶位置
二、Java 并发
1. synchronized 原理
问题:synchronized 的实现原理是什么?
详解:
-
对象头 Mark Word:存储锁状态信息
-
锁升级过程:
-
无锁状态
-
偏向锁(单线程访问)
-
轻量级锁(多线程交替执行)
-
重量级锁(线程竞争激烈)
-
-
底层实现:
-
同步代码块:monitorenter/monitorexit 指令
-
同步方法:ACC_SYNCHRONIZED 标志
-
示例:
public synchronized void method1() {} // 方法锁
public void method2() {
synchronized(this) {} // 对象锁
}
2. ThreadLocal
问题:ThreadLocal 的原理是什么?为什么会内存泄漏?
详解:
-
原理:
-
每个 Thread 维护一个 ThreadLocalMap
-
以 ThreadLocal 为 key,存储线程私有数据
-
-
内存泄漏:
-
ThreadLocalMap 的 key 是弱引用,但 value 是强引用
-
如果 ThreadLocal 被回收,key=null,但 value 仍然存在
-
解决:使用后调用 remove() 方法
-
示例:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("value");
String value = threadLocal.get();
threadLocal.remove(); // 防止内存泄漏
三、Spring 框架
1. Spring Bean 生命周期
问题:请描述 Spring Bean 的生命周期?
详解:
-
实例化(Instantiation)
-
属性赋值(Population)
-
初始化(Initialization)
-
Aware 接口回调(BeanNameAware, BeanFactoryAware)
-
BeanPostProcessor.postProcessBeforeInitialization()
-
@PostConstruct 方法
-
InitializingBean.afterPropertiesSet()
-
自定义 init-method
-
BeanPostProcessor.postProcessAfterInitialization()
-
-
使用(In Use)
-
销毁(Destruction)
-
@PreDestroy 方法
-
DisposableBean.destroy()
-
自定义 destroy-method
-
示例:
public class MyBean implements InitializingBean, DisposableBean {
@PostConstruct
public void init() {
System.out.println("@PostConstruct");
}
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy");
}
@Override
public void destroy() {
System.out.println("DisposableBean");
}
}
2. Spring 事务
问题:Spring 事务的传播机制有哪些?实现原理是什么?
详解:
-
传播机制:
-
REQUIRED(默认):当前有事务则加入,没有则新建
-
REQUIRES_NEW:新建事务,挂起当前事务
-
NESTED:嵌套事务
-
SUPPORTS:有事务则加入,没有则以非事务执行
-
NOT_SUPPORTED:以非事务执行,挂起当前事务
-
NEVER:必须在非事务环境下执行
-
MANDATORY:必须在事务中执行
-
-
实现原理:
-
基于 AOP 动态代理
-
事务管理器(PlatformTransactionManager)
-
事务属性源(TransactionAttributeSource)
-
示例:
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
rollbackFor = Exception.class)
public void transactionalMethod() {
// 业务逻辑
}
四、数据库
1. MySQL 索引
问题:MySQL 索引有哪些类型?B+树索引有什么优势?
详解:
-
索引类型:
-
主键索引(PRIMARY KEY)
-
唯一索引(UNIQUE KEY)
-
普通索引(INDEX)
-
组合索引(联合索引)
-
全文索引(FULLTEXT)
-
-
B+树优势:
-
多路平衡查找树,减少IO次数
-
非叶子节点只存key,叶子节点存数据
-
叶子节点通过指针连接,适合范围查询
-
示例:
-- 创建索引
CREATE INDEX idx_name_age ON user(name, age);
-- 使用索引
EXPLAIN SELECT * FROM user WHERE name = 'John' AND age > 20;
2. 事务隔离级别
问题:MySQL 的事务隔离级别有哪些?分别解决什么问题?
详解:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现方式 |
---|---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 | 不加锁 |
READ COMMITTED | 不可能 | 可能 | 可能 | MVCC |
REPEATABLE READ | 不可能 | 不可能 | 可能 | MVCC+间隙锁 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 | 全表锁 |
示例:
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 查看当前隔离级别
SELECT @@tx_isolation;
五、分布式系统
1. CAP 理论
问题:什么是 CAP 理论?分布式系统如何取舍?
详解:
-
CAP:
-
Consistency(一致性):所有节点数据一致
-
Availability(可用性):每次请求都能获得响应
-
Partition tolerance(分区容错性):系统能容忍网络分区
-
-
取舍:
-
CP:放弃可用性(如 ZooKeeper)
-
AP:放弃一致性(如 Eureka)
-
CA:放弃分区容错(单机系统)
-
2. Redis 持久化
问题:Redis 的持久化机制有哪些?各有什么优缺点?
详解:
-
RDB:
-
定时快照
-
优点:恢复快,文件紧凑
-
缺点:可能丢失最后一次快照后的数据
-
-
AOF:
-
记录写命令
-
优点:数据更完整
-
缺点:文件大,恢复慢
-
-
混合模式(Redis 4.0+):
-
RDB+AOF,结合两者优点
-
配置示例:conf
# redis.conf
save 900 1 # 900秒内至少1个key变化则触发RDB
appendonly yes # 开启AOF
appendfsync everysec # 每秒同步一次AOF
六、系统设计
1. 秒杀系统设计
问题:如何设计一个高并发的秒杀系统?
详解:
-
架构设计:
-
前端:静态化+限流+验证码
-
网关:Nginx 负载均衡+限流
-
服务层:
-
缓存预热(Redis)
-
库存扣减(Redis+Lua脚本)
-
异步下单(消息队列)
-
-
数据层:分库分表
-
-
关键技术:
-
分布式锁(Redisson)
-
限流算法(令牌桶、漏桶)
-
降级熔断(Hystrix/Sentinel)
-
2. 分布式ID生成
问题:分布式系统中如何生成全局唯一ID?
详解:
-
方案对比:
方案 优点 缺点 UUID 简单 无序,存储空间大 数据库自增 简单 单点故障 Redis INCR 性能好 依赖Redis 雪花算法 趋势递增 时钟回拨问题 美团Leaf 高可用 架构复杂 -
雪花算法实现:
public class SnowflakeIdWorker { private long workerId; // 机器ID private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨"); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; } }
七、设计模式
1. 单例模式
问题:如何实现线程安全的单例模式?
详解:
-
双重检查锁:
public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
-
静态内部类:
public class Singleton { private Singleton() {} private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }
2. 工厂模式
问题:工厂模式有哪些变体?各自适用场景?
详解:
-
简单工厂:
public class SimpleFactory { public Product createProduct(String type) { switch(type) { case "A": return new ProductA(); case "B": return new ProductB(); default: throw new IllegalArgumentException(); } } }
-
工厂方法:
public interface Factory { Product createProduct(); } public class FactoryA implements Factory { @Override public Product createProduct() { return new ProductA(); } }
-
抽象工厂:
public interface AbstractFactory { Product1 createProduct1(); Product2 createProduct2(); }
八、算法与数据结构
1. 排序算法
问题:快速排序的实现原理是什么?
详解:
-
分治思想:
-
选择基准元素(pivot)
-
分区:小于pivot的放左边,大于的放右边
-
递归排序左右子序列
-
-
Java实现:
public void quickSort(int[] arr, int low, int high) { if (low < high) { int pivot = partition(arr, low, high); quickSort(arr, low, pivot - 1); quickSort(arr, pivot + 1, high); } } private int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low; for (int j = low; j < high; j++) { if (arr[j] < pivot) { swap(arr, i, j); i++; } } swap(arr, i, high); return i; }
2. LRU 缓存
问题:如何实现一个 LRU 缓存?
详解:
-
LinkedHashMap 实现:
public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return size() > capacity; } }
-
手动实现:
public class LRUCache<K,V> { class Node { K key; V value; Node prev, next; } private final int capacity; private final Map<K, Node> map = new HashMap<>(); private final Node head = new Node(), tail = new Node(); public LRUCache(int capacity) { this.capacity = capacity; head.next = tail; tail.prev = head; } public V get(K key) { Node node = map.get(key); if (node == null) return null; remove(node); addFirst(node); return node.value; } public void put(K key, V value) { Node node = map.get(key); if (node != null) { node.value = value; remove(node); } else { if (map.size() == capacity) { map.remove(tail.prev.key); remove(tail.prev); } node = new Node(); node.key = key; node.value = value; map.put(key, node); } addFirst(node); } private void remove(Node node) { node.prev.next = node.next; node.next.prev = node.prev; } private void addFirst(Node node) { node.next = head.next; node.prev = head; head.next.prev = node; head.next = node; } }