适用目标:Java 后端开发校招、社招初中级面试复习。 复习方式:先掌握“是什么、为什么、怎么用、有什么坑”,再结合项目经历准备追问。

目录


Java 基础

1. Java 有哪些特点?

Java 是一种面向对象、跨平台、自动内存管理的语言。常见特点包括:

  • 跨平台:通过 JVM 实现“一次编译,到处运行”。

  • 面向对象:封装、继承、多态。

  • 自动内存管理:通过 GC 回收无用对象。

  • 生态完善:Spring、MyBatis、Netty、Kafka 等中间件生态成熟。

  • 多线程支持:语言层面提供线程、锁、并发容器和线程池。

2. JDK、JRE、JVM 的区别?

  • JVM:Java 虚拟机,负责执行字节码。

  • JRE:Java 运行环境,包含 JVM 和运行时类库。

  • JDK:Java 开发工具包,包含 JRE、编译器 javac、调试工具等。

开发需要 JDK,运行普通 Java 程序只需要 JRE,不过现代 JDK 发行版通常已经将这些能力整合在一起。

3. ==equals() 的区别?

  • ==

    • 基本类型比较值。

    • 引用类型比较对象地址。

  • equals()

    • 默认实现等价于 ==

    • 很多类会重写,比如 String 比较字符内容。

常见追问:为什么重写 equals() 必须重写 hashCode()

因为哈希容器如 HashMapHashSet 会先用 hashCode() 定位桶,再用 equals() 判断对象是否相等。如果两个对象 equals() 相等,但 hashCode() 不同,会导致集合行为异常。

4. StringStringBuilderStringBuffer 的区别?

  • String:不可变对象,每次修改都会产生新对象。

  • StringBuilder:可变字符序列,线程不安全,性能较好。

  • StringBuffer:可变字符序列,方法加了 synchronized,线程安全,性能略低。

单线程字符串拼接优先用 StringBuilder。多线程共享同一个字符串缓冲区时可考虑 StringBuffer,但实际业务中更常通过局部变量避免共享。

5. final 关键字有什么作用?

  • 修饰类:类不能被继承,例如 String

  • 修饰方法:方法不能被子类重写。

  • 修饰变量:变量只能赋值一次。

注意:final 修饰引用类型时,引用地址不可变,但对象内部状态仍可能改变。

final List<String> list = new ArrayList<>();
list.add("A");       // 可以
// list = new ArrayList<>(); // 不可以

6. Java 异常体系

Java 异常根类是 Throwable,分为:

  • Error:严重错误,一般不处理,如 OutOfMemoryError

  • Exception:程序可处理异常。

    • 受检异常:编译期要求处理,如 IOException

    • 非受检异常:运行期异常,如 NullPointerExceptionIllegalArgumentException

实际开发中,业务异常通常自定义为运行时异常,并通过全局异常处理器统一返回。

7. 深拷贝和浅拷贝

  • 浅拷贝:只复制对象本身,引用字段仍指向原对象中的同一引用。

  • 深拷贝:对象及其引用字段都复制一份,彼此独立。

实现深拷贝的方式:

  • 手动 new 对象并复制字段。

  • 序列化 / 反序列化。

  • JSON 转换。

  • 使用拷贝构造器或专门的映射工具。

8. 反射是什么?有哪些使用场景?

反射是在运行时获取类信息、创建对象、调用方法、访问字段的机制。

常见场景:

  • Spring IOC 创建 Bean。

  • MyBatis 根据映射关系封装对象。

  • 注解解析。

  • 动态代理。

  • 测试框架。

缺点:

  • 性能低于直接调用。

  • 破坏封装性。

  • 编译期类型检查变弱。

9. 注解是什么?

注解是附加在类、方法、字段等元素上的元数据,本身不直接改变程序逻辑,需要结合反射、编译器、框架或 AOP 使用。

常见元注解:

  • @Target:注解可作用的位置。

  • @Retention:注解保留策略。

  • @Documented:是否生成到文档。

  • @Inherited:是否可被子类继承。


面向对象

1. 封装、继承、多态

  • 封装:隐藏内部实现,通过公开方法访问对象状态。

  • 继承:子类复用父类属性和方法。

  • 多态:父类引用指向子类对象,运行时根据真实对象调用方法。

多态成立条件:

  • 有继承或实现关系。

  • 子类重写父类方法。

  • 父类引用指向子类对象。

2. 重载和重写区别

对比项 重载 重写
发生位置 同一个类中 父子类之间
方法名 相同 相同
参数列表 必须不同 必须相同
返回值 可不同,但不能只靠返回值区分 子类返回值可协变
访问权限 无特殊要求 不能比父类更严格
绑定时机 编译期 运行期

3. 抽象类和接口区别

对比项 抽象类 接口
设计目的 表示“是什么” 表示“能做什么”
继承数量 单继承 多实现
成员变量 可有普通变量 默认 public static final
方法 可有普通方法、抽象方法 可有抽象方法、默认方法、静态方法
构造器

经验:抽象类用于提取同类对象的公共状态和行为,接口用于定义能力和规范。

4. 创建对象有哪些方式?

  • new

  • 反射

  • 克隆 clone()

  • 反序列化

  • 工厂方法

  • Spring IOC 容器创建


集合框架

1. Java 集合体系

  • Collection

    • List:有序、可重复,如 ArrayListLinkedList

    • Set:无序、不可重复,如 HashSetTreeSet

    • Queue:队列,如 ArrayDequePriorityQueue

  • Map

    • HashMap

    • LinkedHashMap

    • TreeMap

    • ConcurrentHashMap

2. ArrayListLinkedList 区别

对比项 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 快,O(1) 慢,O(n)
头尾插入删除 可能移动元素 较快
内存占用 较低 较高,需要存前后指针
使用场景 查询多 插入删除多

实际开发中,绝大多数场景优先使用 ArrayList

3. ArrayList 扩容机制

ArrayList 底层是数组。添加元素时,如果容量不足会扩容,通常扩为原容量的 1.5 倍,然后通过数组复制迁移元素。

优化方式:如果能预估集合大小,创建时指定初始容量。

List<String> list = new ArrayList<>(1000);

4. HashMap 底层原理

JDK 8 中,HashMap 底层结构是:

数组 + 链表 + 红黑树

put 流程:

  1. 根据 key 的 hashCode() 计算 hash。

  2. 根据 hash 定位数组下标。

  3. 如果桶为空,直接插入。

  4. 如果桶不为空,比较 hash 和 key。

  5. key 相同则覆盖旧值。

  6. key 不同则插入链表或红黑树。

  7. 元素数量超过阈值触发扩容。

5. 为什么 HashMap 容量是 2 的幂?

主要为了用位运算提高取模效率:

index = (n - 1) & hash;

当容量是 2 的幂时,(n - 1) & hash 等价于 hash % n,并且能让元素分布更均匀。

6. HashMap 什么时候树化?

JDK 8 中,单个桶的链表长度达到 8,且数组容量至少为 64 时,会转为红黑树。

如果数组容量小于 64,优先扩容而不是树化。

7. HashMap 为什么线程不安全?

典型问题:

  • 多线程 put 可能导致数据覆盖。

  • 扩容期间可能出现数据丢失。

  • 并发读写可能读到不一致状态。

并发场景应使用 ConcurrentHashMap

8. ConcurrentHashMap 原理

JDK 7 使用分段锁 Segment。 JDK 8 使用 CAS + synchronized + volatile,锁粒度降低到桶级别。

JDK 8 put 大致流程:

  1. 如果数组未初始化,先初始化。

  2. 如果目标桶为空,用 CAS 插入。

  3. 如果桶不为空,对桶头节点加 synchronized

  4. 插入链表或红黑树。

  5. 必要时协助扩容。

9. HashMapLinkedHashMapTreeMap

  • HashMap:无序,查询快。

  • LinkedHashMap:维护插入顺序或访问顺序,适合实现 LRU。

  • TreeMap:基于红黑树,按 key 排序。


JVM

1. JVM 运行时内存区域

JVM 主要内存区域:

  • 程序计数器:记录当前线程执行的字节码行号。

  • Java 虚拟机栈:存放栈帧、局部变量表、操作数栈等。

  • 本地方法栈:服务 native 方法。

  • 堆:存放对象实例,是 GC 主要区域。

  • 方法区:存放类元信息、常量、静态变量等。

JDK 8 后,永久代被元空间 Metaspace 替代,元空间使用本地内存。

2. 堆和栈的区别

对比项
存储内容 对象实例 方法调用、局部变量
线程共享 共享 线程私有
生命周期 由 GC 管理 随方法调用自动入栈出栈
异常 OutOfMemoryError StackOverflowError

3. 判断对象是否可回收

主流 JVM 使用可达性分析算法。

从 GC Roots 出发,能被直接或间接引用的对象是可达对象,不可达对象可被回收。

常见 GC Roots:

  • 虚拟机栈中引用的对象。

  • 方法区中类静态属性引用的对象。

  • 方法区中常量引用的对象。

  • 本地方法栈中 JNI 引用的对象。

  • 活跃线程对象。

4. 常见垃圾回收算法

  • 标记-清除:先标记可回收对象,再清除。缺点是产生内存碎片。

  • 标记-复制:将存活对象复制到另一块区域。适合新生代。

  • 标记-整理:标记后移动存活对象,整理内存。适合老年代。

  • 分代收集:根据对象生命周期分新生代、老年代采用不同算法。

5. 新生代和老年代

堆通常分为:

  • 新生代:对象创建后先进入这里,生命周期短。

    • Eden

    • Survivor 0

    • Survivor 1

  • 老年代:长期存活对象进入这里。

对象晋升到老年代的情况:

  • 年龄达到阈值。

  • 大对象直接进入老年代。

  • Survivor 空间不足。

  • 动态年龄判断。

6. Minor GC、Major GC、Full GC

  • Minor GC:回收新生代。

  • Major GC:通常指回收老年代,不同语境可能不同。

  • Full GC:回收整个堆和方法区,代价较高。

频繁 Full GC 常见原因:

  • 老年代空间不足。

  • 元空间不足。

  • 大对象频繁创建。

  • 内存泄漏。

  • 显式调用 System.gc()

7. 常见垃圾收集器

  • Serial:单线程,适合小内存客户端场景。

  • ParNew:Serial 多线程版本。

  • Parallel Scavenge:关注吞吐量。

  • CMS:关注低停顿,已逐渐被替代。

  • G1:面向服务端,兼顾吞吐和低停顿。

  • ZGC / Shenandoah:低延迟收集器。

8. 类加载过程

类加载过程:

  1. 加载:读取字节码生成 Class 对象。

  2. 验证:校验字节码安全性。

  3. 准备:为静态变量分配内存并设置默认值。

  4. 解析:符号引用转直接引用。

  5. 初始化:执行静态变量赋值和静态代码块。

9. 双亲委派模型

类加载器收到加载请求后,先委托父加载器加载,父加载器无法加载时才由自己加载。

优点:

  • 避免类重复加载。

  • 保护 Java 核心类库安全。

典型破坏场景:

  • JDBC SPI。

  • Tomcat 多应用隔离。

  • OSGi、热部署框架。

10. JVM 调优常用参数

-Xms512m              # 初始堆大小
-Xmx512m              # 最大堆大小
-Xmn256m              # 新生代大小
-XX:+UseG1GC          # 使用 G1
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heap.hprof

排查工具:

  • jps:查看 Java 进程。

  • jstack:查看线程栈。

  • jmap:查看堆信息、导出 dump。

  • jstat:查看 GC 统计。

  • Arthas:线上诊断常用工具。


并发编程

1. 进程和线程区别

  • 进程:资源分配的基本单位,拥有独立地址空间。

  • 线程:CPU 调度的基本单位,共享进程资源。

线程切换开销小,但共享内存带来线程安全问题。

2. 创建线程的方式

  • 继承 Thread

  • 实现 Runnable

  • 实现 Callable,配合 FutureTask

  • 使用线程池。

实际开发中优先使用线程池,避免频繁创建销毁线程。

3. 线程生命周期

Java 线程状态:

  • NEW

  • RUNNABLE

  • BLOCKED

  • WAITING

  • TIMED_WAITING

  • TERMINATED

4. sleep()wait() 区别

对比项 sleep wait
所属类 Thread Object
是否释放锁 不释放 释放
使用位置 任意位置 必须在同步块或同步方法中
唤醒方式 时间到自动恢复 notify() / notifyAll()

5. synchronized 原理

synchronized 可以修饰方法或代码块,保证同一时刻只有一个线程进入临界区。

底层基于对象监视器 Monitor。进入同步块需要获取 Monitor,退出时释放 Monitor。

JDK 6 后做了大量优化:

  • 偏向锁。

  • 轻量级锁。

  • 重量级锁。

  • 锁粗化。

  • 锁消除。

6. volatile 的作用

volatile 保证:

  • 可见性:一个线程修改变量后,其他线程能及时看到。

  • 有序性:禁止特定指令重排序。

volatile 不保证复合操作的原子性。

volatile int count = 0;
count++; // 不是原子操作

7. JMM 是什么?

JMM 是 Java 内存模型,用于定义线程之间如何通过主内存进行通信,以及可见性、有序性、原子性规则。

核心概念:

  • 主内存。

  • 工作内存。

  • happens-before。

常见 happens-before 规则:

  • 程序顺序规则。

  • volatile 写先行发生于后续读。

  • 解锁先行发生于后续加锁。

  • 线程 start 规则。

  • 线程 join 规则。

8. CAS 是什么?

CAS 是 Compare And Swap,比较并交换。它是一种乐观锁思想。

CAS 操作包含三个值:

  • 内存值 V。

  • 预期值 A。

  • 新值 B。

只有当 V 等于 A 时,才将 V 更新为 B。

问题:

  • ABA 问题。

  • 自旋开销。

  • 只能保证单个变量的原子操作。

ABA 可通过版本号解决,例如 AtomicStampedReference

9. ReentrantLocksynchronized 区别

对比项 synchronized ReentrantLock
实现层面 JVM JDK
是否可重入
公平锁 不支持 支持
可中断 不支持 支持
尝试加锁 不支持 支持 tryLock()
条件队列 一个等待队列 多个 Condition

简单同步优先使用 synchronized,复杂并发控制可使用 ReentrantLock

10. 线程池核心参数

ThreadPoolExecutor 核心参数:

  • corePoolSize:核心线程数。

  • maximumPoolSize:最大线程数。

  • keepAliveTime:非核心线程空闲存活时间。

  • unit:时间单位。

  • workQueue:任务队列。

  • threadFactory:线程工厂。

  • handler:拒绝策略。

执行流程:

  1. 线程数小于核心线程数,创建核心线程。

  2. 核心线程已满,任务进入队列。

  3. 队列满,创建非核心线程。

  4. 达到最大线程数,执行拒绝策略。

11. 常见拒绝策略

  • AbortPolicy:抛异常,默认策略。

  • CallerRunsPolicy:由提交任务的线程执行。

  • DiscardPolicy:直接丢弃。

  • DiscardOldestPolicy:丢弃队列中最旧任务。

生产环境通常自定义拒绝策略,记录日志、报警或降级。

12. 为什么不建议使用 Executors 创建线程池?

因为部分工厂方法存在资源耗尽风险:

  • newFixedThreadPool:队列无界,可能 OOM。

  • newSingleThreadExecutor:队列无界,可能 OOM。

  • newCachedThreadPool:最大线程数极大,可能创建过多线程。

  • newScheduledThreadPool:队列无界。

建议手动创建 ThreadPoolExecutor,明确线程数、队列大小和拒绝策略。

13. ThreadLocal 原理和内存泄漏

每个线程内部有一个 ThreadLocalMap,key 是 ThreadLocal 弱引用,value 是存储的值。

内存泄漏原因:

  • key 是弱引用,可能被 GC。

  • value 是强引用,线程不结束时 value 仍存在。

使用建议:

try {
    threadLocal.set(value);
    // business code
} finally {
    threadLocal.remove();
}

Spring / Spring Boot

1. Spring 核心思想

Spring 核心是 IOC 和 AOP。

  • IOC:控制反转,对象创建和依赖管理交给容器。

  • DI:依赖注入,是 IOC 的实现方式。

  • AOP:面向切面编程,将日志、事务、权限等横切逻辑从业务中抽离。

2. Bean 生命周期

典型生命周期:

  1. 实例化 Bean。

  2. 属性注入。

  3. 执行 Aware 接口回调。

  4. BeanPostProcessor 前置处理。

  5. 初始化方法,如 @PostConstructafterPropertiesSet()init-method

  6. BeanPostProcessor 后置处理。

  7. Bean 可使用。

  8. 容器关闭时执行销毁方法。

3. Bean 作用域

  • singleton:单例,默认。

  • prototype:每次获取创建新实例。

  • request:每个 HTTP 请求一个实例。

  • session:每个 HTTP Session 一个实例。

  • application:ServletContext 级别。

4. Spring 如何解决循环依赖?

Spring 主要通过三级缓存解决单例 Bean 的 setter 循环依赖:

  • 一级缓存:完整 Bean。

  • 二级缓存:早期 Bean。

  • 三级缓存:ObjectFactory,用于提前暴露对象或代理对象。

不能解决的情况:

  • 构造器循环依赖。

  • prototype Bean 循环依赖。

5. AOP 原理

Spring AOP 基于动态代理:

  • 如果目标类实现接口,默认使用 JDK 动态代理。

  • 如果目标类没有实现接口,使用 CGLIB 代理。

常见术语:

  • 切面 Aspect。

  • 连接点 JoinPoint。

  • 切点 Pointcut。

  • 通知 Advice。

  • 织入 Weaving。

常见应用:

  • 声明式事务。

  • 日志记录。

  • 权限校验。

  • 接口限流。

6. Spring 事务传播行为

常见传播行为:

  • REQUIRED:有事务则加入,没有则新建,默认。

  • REQUIRES_NEW:新建事务,挂起当前事务。

  • NESTED:嵌套事务。

  • SUPPORTS:有事务则加入,没有就非事务执行。

  • MANDATORY:必须在事务中执行。

  • NOT_SUPPORTED:非事务执行,挂起当前事务。

  • NEVER:必须非事务执行。

7. 事务失效常见原因

  • 方法不是 public

  • 同类方法内部调用。

  • 异常被捕获未抛出。

  • 抛出的异常不是默认回滚异常。

  • 数据库引擎不支持事务。

  • 未被 Spring 管理。

  • 多线程中事务上下文丢失。

默认情况下,Spring 事务只对运行时异常和 Error 回滚。受检异常需要配置:

@Transactional(rollbackFor = Exception.class)

8. Spring Boot 自动装配原理

Spring Boot 通过自动配置类,根据 classpath、配置文件和条件注解自动创建 Bean。

核心机制:

  • @SpringBootApplication

    • @SpringBootConfiguration

    • @EnableAutoConfiguration

    • @ComponentScan

  • 自动配置导入。

  • 条件注解,如 @ConditionalOnClass@ConditionalOnMissingBean

9. Spring MVC 请求流程

  1. 请求进入 DispatcherServlet

  2. 通过 HandlerMapping 找到处理器。

  3. 通过 HandlerAdapter 调用 Controller。

  4. Controller 执行业务逻辑。

  5. 返回 ModelAndView 或 JSON。

  6. 视图解析或消息转换。

  7. 响应客户端。

10. 常用注解

  • @Component:通用组件。

  • @Service:业务层。

  • @Repository:持久层。

  • @Controller / @RestController:控制层。

  • @Autowired:自动注入。

  • @Resource:按名称优先注入。

  • @Configuration:配置类。

  • @Bean:声明 Bean。

  • @Transactional:事务。


MyBatis

1. MyBatis 是什么?

MyBatis 是半自动 ORM 框架。开发者手写 SQL,MyBatis 负责参数映射、SQL 执行和结果映射。

优点:

  • SQL 灵活,适合复杂查询。

  • 学习成本低。

  • 与 Spring 集成成熟。

缺点:

  • 需要维护 SQL。

  • 数据库迁移成本相对更高。

2. #{}${} 区别

  • #{}:预编译参数,占位符,能防止 SQL 注入。

  • ${}:字符串拼接,存在 SQL 注入风险。

排序字段、表名等不能使用 #{} 的场景,必须使用 ${} 时要做白名单校验。

3. MyBatis 一级缓存和二级缓存

  • 一级缓存:SqlSession 级别,默认开启。

  • 二级缓存:Mapper namespace 级别,需要手动开启。

实际项目中二级缓存较少使用,因为多表更新、分布式场景下容易产生一致性问题。

4. MyBatis 动态 SQL

常见标签:

  • <if>

  • <where>

  • <set>

  • <foreach>

  • <choose>

  • <trim>

批量查询常用:

<foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
</foreach>

5. MyBatis 分页

常见方式:

  • SQL 中使用 limit

  • 使用 PageHelper 插件。

  • 自定义拦截器。

大数据量分页不要使用过深的 offset,可使用游标分页或基于 ID 的分页。


MySQL

1. MySQL 存储引擎

常见存储引擎:

  • InnoDB:支持事务、行锁、外键、崩溃恢复,是默认引擎。

  • MyISAM:不支持事务,表锁,读性能较好,但现在较少使用。

2. 事务 ACID

  • Atomicity 原子性:事务要么全部成功,要么全部失败。

  • Consistency 一致性:事务前后数据满足约束。

  • Isolation 隔离性:并发事务之间互不干扰。

  • Durability 持久性:提交后的数据不会丢失。

3. 事务隔离级别

隔离级别 脏读 不可重复读 幻读
Read Uncommitted 可能 可能 可能
Read Committed 不可能 可能 可能
Repeatable Read 不可能 不可能 InnoDB 通常可避免
Serializable 不可能 不可能 不可能

MySQL InnoDB 默认隔离级别是 Repeatable Read

4. MVCC 原理

MVCC 是多版本并发控制,用于提高并发读写性能。

核心依赖:

  • 隐藏字段:事务 ID、回滚指针。

  • undo log:保存历史版本。

  • ReadView:判断版本可见性。

快照读通过 MVCC 实现,当前读会加锁读取最新数据。

快照读示例:

select * from user where id = 1;

当前读示例:

select * from user where id = 1 for update;
update user set name = 'A' where id = 1;

5. 索引数据结构

InnoDB 索引底层主要使用 B+ 树。

B+ 树适合数据库索引的原因:

  • 树高度低,磁盘 IO 少。

  • 非叶子节点只存 key,能放更多索引项。

  • 叶子节点有序链表,适合范围查询。

6. 聚簇索引和非聚簇索引

  • 聚簇索引:叶子节点存整行数据,InnoDB 主键索引就是聚簇索引。

  • 二级索引:叶子节点存主键值,查询时可能需要回表。

回表:通过二级索引找到主键,再通过主键索引查询整行数据。

覆盖索引:查询字段都在索引中,无需回表。

7. 最左前缀原则

联合索引按照从左到右匹配。

例如索引:

idx(a, b, c)

可命中:

where a = 1
where a = 1 and b = 2
where a = 1 and b = 2 and c = 3

不能充分命中:

where b = 2
where c = 3

8. 索引失效常见场景

  • 对索引列使用函数。

  • 对索引列进行计算。

  • 使用左模糊查询,如 like '%abc'

  • 类型隐式转换。

  • 联合索引不满足最左前缀。

  • or 两边不是都有索引。

  • 低选择性字段不适合建索引。

9. 如何优化 SQL?

常见思路:

  • 使用 explain 分析执行计划。

  • 避免 select *

  • 为高频查询条件建立合适索引。

  • 使用覆盖索引减少回表。

  • 避免索引失效。

  • 控制返回数据量。

  • 大表分页优化。

  • 拆分复杂 SQL。

  • 合理设计表结构。

10. explain 重点看什么?

重点字段:

  • type:访问类型,至少达到 range,最好 refconst

  • key:实际使用的索引。

  • rows:预估扫描行数。

  • Extra:额外信息,如 Using indexUsing filesortUsing temporary

type 常见性能从好到差:

system > const > eq_ref > ref > range > index > ALL

11. MySQL 锁

按粒度:

  • 表锁。

  • 行锁。

  • 间隙锁。

  • 临键锁。

InnoDB 行锁基于索引实现。如果条件没有命中索引,可能退化为更大范围的锁。

12. 主从复制原理

MySQL 主从复制大致流程:

  1. 主库写入 binlog。

  2. 从库 IO 线程读取主库 binlog,写入 relay log。

  3. 从库 SQL 线程读取 relay log 并重放。

可能问题:

  • 主从延迟。

  • 数据一致性。

  • 网络抖动。

  • 大事务导致延迟。


Redis

1. Redis 为什么快?

主要原因:

  • 基于内存。

  • 单线程事件循环避免线程切换和锁竞争。

  • IO 多路复用。

  • 高效数据结构。

  • C 语言实现,代码执行效率高。

Redis 6 引入多线程主要用于网络 IO,命令执行仍以单线程为主。

2. Redis 常见数据结构

  • String:缓存、计数器、分布式锁。

  • Hash:对象存储。

  • List:消息队列、列表。

  • Set:去重、共同好友。

  • ZSet:排行榜、延时队列。

  • Bitmap:签到、活跃统计。

  • HyperLogLog:UV 统计。

  • Stream:消息流。

3. Redis 持久化

  • RDB:快照持久化。

    • 优点:文件紧凑,恢复快。

    • 缺点:可能丢失最近一次快照后的数据。

  • AOF:追加日志。

    • 优点:数据安全性更高。

    • 缺点:文件更大,恢复较慢。

生产环境常结合 RDB + AOF。

4. Redis 过期删除策略

Redis 删除过期 key 的策略:

  • 惰性删除:访问 key 时检查是否过期。

  • 定期删除:周期性抽样删除过期 key。

如果内存仍不足,会触发内存淘汰策略。

5. Redis 内存淘汰策略

常见策略:

  • noeviction:不淘汰,写入报错。

  • allkeys-lru:所有 key 中淘汰最近最少使用。

  • volatile-lru:设置过期时间的 key 中淘汰 LRU。

  • allkeys-random:所有 key 随机淘汰。

  • volatile-random:过期 key 中随机淘汰。

  • volatile-ttl:优先淘汰即将过期的 key。

  • allkeys-lfu:所有 key 中淘汰低频使用。

  • volatile-lfu:过期 key 中淘汰低频使用。

6. 缓存穿透、击穿、雪崩

缓存穿透

查询不存在的数据,请求直接打到数据库。

解决:

  • 缓存空值。

  • 布隆过滤器。

  • 参数校验。

缓存击穿

热点 key 过期,大量请求同时打到数据库。

解决:

  • 互斥锁。

  • 逻辑过期。

  • 热点 key 永不过期。

  • 提前异步刷新。

缓存雪崩

大量 key 同时过期或 Redis 故障。

解决:

  • 过期时间加随机值。

  • 多级缓存。

  • 限流降级。

  • Redis 高可用。

7. Redis 分布式锁

基础命令:

SET lock_key unique_value NX PX 30000

释放锁要用 Lua 脚本保证“判断 value + 删除 key”的原子性。

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

需要注意:

  • 锁超时时间。

  • 业务执行时间超过锁时间。

  • 锁误删。

  • 主从切换导致锁丢失。

生产可使用 Redisson,支持看门狗自动续期。

8. Redis 主从、哨兵、集群

  • 主从复制:读写分离,提高读能力。

  • 哨兵 Sentinel:监控、故障转移、通知。

  • Cluster:数据分片,支持水平扩展。

Redis Cluster 使用 16384 个 hash slot 分配数据。

9. Redis 和 MySQL 数据一致性

常用方案:先更新数据库,再删除缓存。

为什么不是更新缓存?

  • 并发下容易覆盖新值。

  • 有些缓存值不是简单从数据库字段映射来的。

进一步优化:

  • 删除缓存失败重试。

  • 使用消息队列补偿。

  • 监听 binlog 异步删除缓存。

  • 设置合理过期时间兜底。


消息队列

1. 为什么使用消息队列?

主要作用:

  • 异步处理:提升接口响应速度。

  • 削峰填谷:缓冲高峰流量。

  • 系统解耦:生产者和消费者解耦。

  • 顺序处理:部分业务需要按顺序消费。

缺点:

  • 系统复杂度增加。

  • 可能出现消息丢失。

  • 可能出现重复消费。

  • 可能出现消息积压。

  • 数据一致性更复杂。

2. 如何保证消息不丢失?

从三个阶段考虑:

  • 生产者到 MQ:

    • 开启 confirm 或事务机制。

    • 失败重试。

  • MQ 自身:

    • 持久化。

    • 多副本。

  • MQ 到消费者:

    • 手动 ack。

    • 消费失败重试。

    • 死信队列。

3. 如何解决重复消费?

消息队列通常只能尽量保证至少一次投递,因此消费者要保证幂等。

常见方案:

  • 数据库唯一索引。

  • 业务状态机。

  • Redis 去重。

  • 消息表记录消费状态。

4. 如何保证消息顺序?

思路:同一业务 key 的消息进入同一个队列或分区,由同一个消费者顺序处理。

例如订单消息按 orderId 路由到同一分区。

注意:全局有序性能较差,实际更多是局部有序。

5. 消息积压怎么办?

排查方向:

  • 消费者是否异常。

  • 消费逻辑是否慢。

  • MQ 分区或队列数是否不足。

  • 下游数据库是否瓶颈。

处理方式:

  • 临时扩容消费者。

  • 优化消费逻辑。

  • 批量消费。

  • 增加分区或队列。

  • 先落库或转储后慢慢补偿。


计算机网络

1. OSI 七层模型和 TCP/IP 四层模型

OSI 七层:

应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

TCP/IP 四层:

应用层、传输层、网络层、网络接口层

常见协议:

  • HTTP / HTTPS:应用层。

  • TCP / UDP:传输层。

  • IP:网络层。

  • ARP:网络接口相关。

2. TCP 和 UDP 区别

对比项 TCP UDP
连接 面向连接 无连接
可靠性 可靠 不可靠
顺序性 保证顺序 不保证
速度 较慢 较快
场景 Web、文件传输 直播、游戏、DNS

3. TCP 三次握手

流程:

  1. 客户端发送 SYN。

  2. 服务端返回 SYN + ACK。

  3. 客户端返回 ACK。

为什么不是两次?

为了确认双方收发能力都正常,避免历史连接请求导致错误连接。

4. TCP 四次挥手

流程:

  1. 主动关闭方发送 FIN。

  2. 被动关闭方返回 ACK。

  3. 被动关闭方处理完数据后发送 FIN。

  4. 主动关闭方返回 ACK。

为什么需要四次?

因为 TCP 是全双工,双方发送通道需要分别关闭。

5. HTTP 和 HTTPS 区别

  • HTTP 明文传输,默认 80 端口。

  • HTTPS 基于 HTTP + TLS,默认 443 端口。

HTTPS 提供:

  • 加密传输。

  • 身份认证。

  • 完整性校验。

6. HTTP 状态码

  • 200:成功。

  • 301:永久重定向。

  • 302:临时重定向。

  • 400:请求参数错误。

  • 401:未认证。

  • 403:无权限。

  • 404:资源不存在。

  • 500:服务端异常。

  • 502:网关错误。

  • 503:服务不可用。

  • 504:网关超时。

7. GET 和 POST 区别

常见区别:

  • GET 通常用于查询,POST 通常用于提交。

  • GET 参数一般在 URL 中,POST 参数通常在请求体。

  • GET 更容易被缓存。

  • GET 幂等,POST 通常不幂等。

注意:安全性不取决于 GET/POST,而取决于是否使用 HTTPS、鉴权和参数保护。

8. 浏览器输入 URL 后发生了什么?

简化流程:

  1. URL 解析。

  2. DNS 解析域名。

  3. 建立 TCP 连接。

  4. HTTPS 场景进行 TLS 握手。

  5. 发送 HTTP 请求。

  6. 服务端处理请求。

  7. 返回响应。

  8. 浏览器解析渲染页面。

  9. 连接复用或关闭。


Linux 与排查

1. 常用命令

top                 # 查看系统资源
ps -ef | grep java  # 查看 Java 进程
netstat -tulnp      # 查看端口
ss -tulnp           # 查看端口
df -h               # 查看磁盘
du -sh *            # 查看目录大小
free -m             # 查看内存
tail -f app.log     # 实时日志
grep "ERROR" app.log

2. CPU 飙高如何排查?

步骤:

  1. top 找到 CPU 高的 Java 进程 PID。

  2. top -Hp PID 找到高 CPU 线程 TID。

  3. 将 TID 转 16 进制。

  4. jstack PID 查看线程栈。

  5. 根据线程栈定位代码。

printf "%x\n" 12345
jstack <pid> | grep -A 30 <nid>

3. 内存溢出如何排查?

常见步骤:

  1. 查看错误日志,确认 OOM 类型。

  2. 导出堆 dump。

  3. 使用 MAT、VisualVM、JProfiler 分析大对象。

  4. 找 GC Roots 引用链。

  5. 修复内存泄漏或调整参数。

建议启动参数:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump

4. 接口变慢如何排查?

排查路径:

  • 看应用日志耗时。

  • 看数据库慢 SQL。

  • 看 Redis、MQ、第三方接口耗时。

  • 看线程池是否满。

  • 看 GC 是否频繁。

  • 看机器 CPU、内存、磁盘 IO、网络。

  • 看链路追踪。


分布式与微服务

1. 分布式系统常见问题

  • 服务调用失败。

  • 网络延迟和超时。

  • 数据一致性。

  • 分布式事务。

  • 服务雪崩。

  • 配置管理。

  • 链路追踪。

  • 日志聚合。

2. CAP 理论

CAP:

  • Consistency:一致性。

  • Availability:可用性。

  • Partition tolerance:分区容错性。

分布式系统中网络分区不可避免,所以通常在 CP 和 AP 之间权衡。

3. BASE 理论

BASE 是对强一致性的弱化:

  • Basically Available:基本可用。

  • Soft State:软状态。

  • Eventually Consistent:最终一致性。

很多互联网系统选择最终一致性。

4. 分布式事务解决方案

常见方案:

  • 2PC:强一致,但性能差、阻塞风险。

  • TCC:Try、Confirm、Cancel,业务侵入强。

  • 本地消息表:最终一致,可靠性高。

  • MQ 事务消息:适合异步最终一致。

  • Saga:长事务拆分,逐步补偿。

5. 分布式 ID

常见方案:

  • 数据库自增 ID。

  • UUID。

  • Redis 自增。

  • Snowflake 雪花算法。

  • 号段模式。

Snowflake 通常包含:

  • 时间戳。

  • 机器 ID。

  • 序列号。

优点是趋势递增、性能高、适合分布式。

6. 限流算法

  • 固定窗口:简单,但边界可能突刺。

  • 滑动窗口:更平滑。

  • 漏桶:恒定速率处理请求。

  • 令牌桶:允许一定突发流量。

常见限流位置:

  • 网关。

  • Nginx。

  • 应用接口。

  • Redis + Lua。

7. 熔断、降级、隔离

  • 熔断:服务异常比例过高时,暂时停止调用。

  • 降级:返回兜底数据或关闭非核心功能。

  • 隔离:线程池、信号量、资源隔离,避免故障扩散。

8. 服务注册与发现

常见组件:

  • Nacos。

  • Eureka。

  • Consul。

  • ZooKeeper。

流程:

  1. 服务启动后注册到注册中心。

  2. 消费者从注册中心获取服务列表。

  3. 客户端负载均衡选择实例。

  4. 通过心跳维持服务状态。

9. 网关作用

网关常见职责:

  • 路由转发。

  • 鉴权。

  • 限流。

  • 灰度发布。

  • 日志记录。

  • 跨域处理。

  • 协议转换。


项目面试高频追问

1. 介绍一下你的项目

建议回答结构:

  1. 项目背景:解决什么问题。

  2. 技术栈:Spring Boot、MyBatis、MySQL、Redis、MQ 等。

  3. 个人职责:你负责哪些模块。

  4. 核心难点:性能、一致性、并发、可用性。

  5. 结果数据:QPS、响应时间、数据量、稳定性提升。

2. 登录认证怎么做?

常见方案:

  • Session + Cookie。

  • JWT。

  • OAuth2。

  • 单点登录 SSO。

JWT 流程:

  1. 用户登录成功后生成 token。

  2. token 返回客户端。

  3. 后续请求携带 token。

  4. 服务端校验签名和过期时间。

  5. 从 token 或 Redis 中获取用户信息。

注意问题:

  • token 过期。

  • token 续期。

  • 退出登录。

  • 权限变更后 token 失效。

  • 防止 token 被盗用。

3. 权限系统怎么设计?

常见模型:RBAC。

用户 -> 角色 -> 权限

常见表:

  • 用户表。

  • 角色表。

  • 权限表。

  • 用户角色关联表。

  • 角色权限关联表。

接口权限可通过拦截器、过滤器、AOP 或 Spring Security 实现。

4. 秒杀系统怎么设计?

核心问题:

  • 高并发。

  • 防超卖。

  • 防刷。

  • 异步削峰。

  • 数据一致性。

常见方案:

  • 页面静态化。

  • CDN。

  • 接口限流。

  • Redis 预扣库存。

  • Lua 保证原子性。

  • MQ 异步下单。

  • 数据库乐观锁兜底。

  • 幂等控制。

5. 如何防止重复提交?

方案:

  • 前端按钮置灰。

  • token 防重复提交。

  • 数据库唯一索引。

  • Redis setnx

  • 幂等号。

  • 状态机控制。

6. 如何设计幂等接口?

常见思路:

  • 请求唯一 ID。

  • 数据库唯一约束。

  • Redis 去重。

  • 乐观锁版本号。

  • 业务状态机。

例如支付回调:

  • 先查询订单状态。

  • 已支付则直接返回成功。

  • 未支付则更新状态。

  • 更新条件中带原状态。

update order_info
set status = 'PAID'
where order_no = ? and status = 'UNPAID';

7. 如何做接口限流?

可选方案:

  • 本地限流:Guava RateLimiter、Sentinel。

  • 分布式限流:Redis + Lua。

  • 网关限流:Spring Cloud Gateway、Nginx。

需要考虑:

  • 限流维度:用户、IP、接口、租户。

  • 超限响应。

  • 白名单。

  • 监控告警。

8. 如何保证接口安全?

常见措施:

  • HTTPS。

  • 登录认证。

  • 权限校验。

  • 参数校验。

  • 签名机制。

  • 时间戳 + nonce 防重放。

  • SQL 注入防护。

  • XSS / CSRF 防护。

  • 敏感信息脱敏。


手写题与场景题

1. 手写单例模式

静态内部类方式:

public class Singleton {
    private Singleton() {}
​
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
​
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

优点:懒加载、线程安全、实现简单。

2. 手写双重检查锁单例

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile 用于防止指令重排序,避免其他线程拿到未初始化完成的对象。

3. 手写 LRU 缓存

import java.util.LinkedHashMap;
import java.util.Map;

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;
    }
}

4. 两个线程交替打印

public class AlternatePrint {
    private static final Object LOCK = new Object();
    private static int num = 1;
​
    public static void main(String[] args) {
        Runnable task = () -> {
            while (true) {
                synchronized (LOCK) {
                    if (num > 100) {
                        LOCK.notifyAll();
                        break;
                    }
                    System.out.println(Thread.currentThread().getName() + ": " + num++);
                    LOCK.notifyAll();
                    try {
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        };
​
        new Thread(task, "A").start();
        new Thread(task, "B").start();
    }
}

5. 生产者消费者模型

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
​
public class ProducerConsumer {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
​
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    queue.put(i);
                    System.out.println("produce: " + i);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
​
        Thread consumer = new Thread(() -> {
            while (true) {
                try {
                    Integer value = queue.take();
                    System.out.println("consume: " + value);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
​
        producer.start();
        consumer.start();
    }
}

6. 场景题:如何设计排行榜?

可以使用 Redis ZSet。

  • 用户 ID 作为 member。

  • 分数作为 score。

  • ZADD 更新分数。

  • ZREVRANGE 查询榜单。

  • ZREVRANK 查询排名。

需要考虑:

  • 分数相同的排序规则。

  • 榜单周期:日榜、周榜、总榜。

  • 数据持久化到 MySQL。

  • 热点榜单缓存。

7. 场景题:如何设计延时任务?

常见方案:

  • Redis ZSet:score 存执行时间。

  • MQ 延迟队列。

  • 时间轮。

  • 数据库定时扫描。

Redis ZSet 方案:

  1. 任务写入 ZSet,score 是执行时间戳。

  2. 定时扫描到期任务。

  3. 抢占成功后执行任务。

  4. 执行失败进入重试或死信。

需要考虑任务重复执行、宕机恢复和执行幂等。


高频速记

Java

  • String 不可变,拼接多用 StringBuilder

  • equals() 相等必须保证 hashCode() 相等。

  • 反射常用于框架,缺点是性能和封装性。

集合

  • HashMap 是数组 + 链表 + 红黑树。

  • ConcurrentHashMap JDK 8 是 CAS + synchronized。

  • ArrayList 查询快,LinkedList 插入删除灵活但内存开销大。

JVM

  • 堆存对象,栈存方法调用。

  • GC Roots 可达性分析判断对象是否回收。

  • G1 是常见服务端垃圾收集器。

  • 双亲委派保护核心类,避免重复加载。

并发

  • volatile 保证可见性和有序性,不保证复合操作原子性。

  • CAS 是乐观锁,但有 ABA 问题。

  • 线程池核心参数必须会背。

  • ThreadLocal 用完要 remove()

Spring

  • IOC 管对象,AOP 管切面。

  • Spring 三级缓存解决单例 setter 循环依赖。

  • 事务失效重点看代理、异常、方法可见性、同类调用。

MySQL

  • InnoDB 支持事务和行锁。

  • B+ 树适合范围查询和减少磁盘 IO。

  • 联合索引遵循最左前缀。

  • MVCC 依赖 undo log 和 ReadView。

Redis

  • Redis 快的原因:内存、单线程、IO 多路复用、高效结构。

  • 缓存穿透、击穿、雪崩要会区分。

  • 分布式锁用 SET NX PX + Lua 解锁。

分布式

  • MQ 用于异步、削峰、解耦。

  • 重复消费靠幂等。

  • 分布式事务通常追求最终一致。

  • 限流、熔断、降级、隔离是高可用常见手段。


面试回答模板

技术点回答模板

这个问题我会从四个方面回答:
第一,它是什么;
第二,为什么要用它;
第三,它的底层原理或核心流程;
第四,实际项目中有哪些注意点或坑。

项目亮点回答模板

这个模块我主要负责 XXX。
当时遇到的问题是 XXX。
我采用了 XXX 方案,核心思路是 XXX。
落地过程中重点处理了 XXX,比如并发、一致性、幂等、性能。
最终效果是 XXX,比如接口耗时降低、稳定性提升、支持更高并发。

场景题回答模板

我会先明确业务目标和约束,然后从数据模型、核心流程、并发控制、
异常补偿、监控告警和扩展性几个方面设计。
如果是高并发场景,还会考虑缓存、限流、异步削峰和降级方案。

复习建议

  1. 第一轮:通读所有模块,建立知识地图。

  2. 第二轮:重点背 Java 集合、JVM、并发、Spring、MySQL、Redis。

  3. 第三轮:结合自己的项目,把每个技术点落到项目场景中。

  4. 第四轮:模拟面试,用“是什么、为什么、怎么做、有什么坑”回答。

  5. 面试前一天:重点复习高频速记、项目追问和手写题。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐