📌 Java后端篇(15题)
1. 说说JVM的内存结构?
答案框架(记忆口诀:堆栈方本程)
JVM内存分为5个区域:
- 堆(Heap):存放对象实例,是GC的主要区域,分为新生代(Eden、S0、S1)和老年代
- 栈(Stack):每个线程私有,存局部变量、方法调用,栈帧包含局部变量表、操作数栈、动态链接、返回地址
- 方法区(Method Area):存类信息、常量、静态变量,JDK8后叫元空间(Metaspace)使用直接内存
- 本地方法栈:为Native方法服务
- 程序计数器:线程私有,记录当前执行的字节码行号
面试加分项:堆是线程共享的,栈是线程私有的;新生代和老年代比例默认1:2
2. Java的垃圾回收机制(GC)?
答案框架(记忆:识别、算法、收集器)
如何识别垃圾:
- 引用计数法(循环引用问题)
- 可达性分析算法:从GC Roots出发,不可达的对象是垃圾
GC算法:
- 标记-清除:产生碎片
- 标记-整理:老年代用,整理内存
- 复制算法:新生代用,Eden和Survivor区8:1:1
- 分代收集:新生代复制,老年代标记整理
常见收集器:
- Serial:单线程,适合客户端
- Parallel:多线程,吞吐量优先
- CMS:并发标记清除,停顿时间短
- G1:JDK9默认,面向服务端,可预测停顿
- ZGC:JDK11,超低延迟
3. HashMap底层原理?JDK1.7和1.8的区别?
底层结构:
- JDK1.7:数组+链表
- JDK1.8:数组+链表+红黑树(链表长度>8且数组长度>=64时转红黑树)
put流程:
- 计算key的hash值
- 根据hash值找到数组下标
- 如果位置为空,直接插入
- 如果不为空,判断key是否相同,相同则覆盖value
- 不同则遍历链表/红黑树插入
关键参数:
- 初始容量:16
- 负载因子:0.75
- 扩容:容量翻倍,重新hash(JDK1.8优化了rehash)
1.7和1.8区别:
- 1.7:头插法(并发可能死循环),先扩容后插入
- 1.8:尾插法,先插入后扩容,引入红黑树
4. ConcurrentHashMap实现原理?
JDK1.7:分段锁(Segment数组),每个Segment是ReentrantLock
JDK1.8:
- 取消Segment,使用Node数组+CAS+synchronized
- 锁粒度更细:锁住数组的某个位置(链表或红黑树头节点)
- put流程:CAS尝试插入,失败则synchronized锁住头节点
为什么线程安全:
- 初始化用CAS
- 添加元素用synchronized锁住桶
- 扩容时协助扩容(多线程)
5. Spring Bean的生命周期?
记忆口诀:实例化、填充、初始化、使用、销毁
- 实例化:通过反射创建Bean实例
- 属性填充:依赖注入(@Autowired)
- Aware接口回调:BeanNameAware、BeanFactoryAware等
- BeanPostProcessor前置处理:postProcessBeforeInitialization
- 初始化:@PostConstruct、InitializingBean、init-method
- BeanPostProcessor后置处理:postProcessAfterInitialization(AOP在这里)
- 使用Bean
- 销毁:@PreDestroy、DisposableBean、destroy-method
面试加分项:循环依赖用三级缓存解决(单例、非构造器注入)
6. Spring的事务传播机制?
7种传播行为(记重点3个):
必记:
- REQUIRED(默认):有事务加入,没有创建新的
- REQUIRES_NEW:总是创建新事务,挂起当前事务
- NESTED:嵌套事务,外层回滚内层也回滚,内层回滚外层可以不回滚
其他:
- SUPPORTS:有就用,没有就非事务
- NOT_SUPPORTED:非事务执行,挂起当前事务
- MANDATORY:必须有事务,否则抛异常
- NEVER:必须非事务,否则抛异常
失效场景:
- 方法不是public
- 同一类内部调用(没走代理)
- 异常被catch没抛出
- 抛出的不是RuntimeException或Error
7. Spring Boot自动配置原理?
核心注解:@SpringBootApplication=@Configuration+@EnableAutoConfiguration+@ComponentScan
自动配置流程:
@EnableAutoConfiguration导入AutoConfigurationImportSelector- 读取
spring.factories文件的配置类全限定名 - 通过
@Conditional条件注解判断是否生效 - 满足条件则加载配置类到容器
常见@Conditional:
- @ConditionalOnClass:类存在
- @ConditionalOnMissingBean:Bean不存在
- @ConditionalOnProperty:配置属性匹配
8. MySQL索引原理?为什么用B+树?
索引类型:
- 主键索引(聚簇索引):叶子节点存数据
- 普通索引(二级索引):叶子节点存主键值,需要回表查询
- 唯一索引、全文索引
为什么用B+树:
- B+树vs二叉树:B+树高度低,磁盘IO次数少(百万数据3-4层)
- B+树vs B树:
- B+树非叶子节点不存数据,能存更多索引,扇出更大
- B+树叶子节点有链表,范围查询效率高
- B+树更稳定,查询都是从根到叶子
面试加分项:
- 聚簇索引:InnoDB主键索引
- 非聚簇索引:MyISAM,叶子节点存地址
- 回表:通过二级索引查到主键,再用主键查完整数据
9. MySQL事务隔离级别?如何解决幻读?
4种隔离级别(记忆:未读提不可串):
- 读未提交(READ UNCOMMITTED):脏读、不可重复读、幻读都有
- 读已提交(READ COMMITTED):解决脏读,Oracle默认
- 可重复读(REPEATABLE READ):解决不可重复读,MySQL默认,通过MVCC+间隙锁解决幻读
- 串行化(SERIALIZABLE):全部解决,但性能差
问题定义:
- 脏读:读到未提交的数据
- 不可重复读:同一事务两次读数据不一致(其他事务UPDATE)
- 幻读:同一事务两次查询记录数不一致(其他事务INSERT/DELETE)
MySQL如何解决幻读:
- MVCC(多版本并发控制):快照读,读历史版本
- Next-Key Lock(记录锁+间隙锁):当前读,锁住范围
10. Redis数据类型及应用场景?
5种基本类型:
- String:缓存、计数器、分布式锁(SETNX)
- Hash:存对象,如用户信息
- List:消息队列、文章列表、LPUSH+RPOP
- Set:去重、共同好友、抽奖(SRANDMEMBER)
- ZSet(有序集合):排行榜、延时队列
3种特殊类型:
- HyperLogLog:UV统计(基数统计)
- Bitmap:签到、布隆过滤器
- GEO:地理位置
11. Redis持久化机制?
两种方式:
RDB(快照):
- 优点:文件小,恢复快
- 缺点:可能丢失最后一次快照后的数据
- 触发:SAVE、BGSAVE、自动触发(save 900 1)
AOF(Append Only File):
- 优点:数据完整性好
- 缺点:文件大,恢复慢
- 策略:always(每次写)、everysec(每秒)、no(操作系统决定)
混合持久化(4.0+):
- RDB作为基础,AOF记录增量
- 兼顾恢复速度和数据安全
12. Redis缓存穿透、击穿、雪崩?
缓存穿透:查询不存在的数据,缓存和数据库都没有
- 解决:布隆过滤器、缓存空值
缓存击穿:热点key过期,大量请求打到数据库
- 解决:热点数据永不过期、互斥锁(setnx)
缓存雪崩:大量key同时过期或Redis宕机
- 解决:过期时间加随机值、Redis集群、限流降级
13. 分布式锁的实现方式?
Redis实现:
// 加锁:SETNX + 过期时间(原子性) SET lock_key unique_value NX PX 30000 // 解锁:Lua脚本保证原子性 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end注意点:
- value要唯一(UUID),防止误删
- 设置过期时间,防止死锁
- 使用Redisson框架(看门狗机制)
其他实现:
- Zookeeper:临时顺序节点
- 数据库:乐观锁(version)、悲观锁(for update)
14. 线程池核心参数及执行流程?
7个核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- unit:时间单位
- workQueue:阻塞队列
- threadFactory:线程工厂
- handler:拒绝策略
执行流程:
- 线程数 < corePoolSize:创建核心线程
- 线程数 >= corePoolSize:放入队列
- 队列满 && 线程数 < maximumPoolSize:创建非核心线程
- 线程数 >= maximumPoolSize:执行拒绝策略
4种拒绝策略:
- AbortPolicy:抛异常(默认)
- CallerRunsPolicy:调用者线程执行
- DiscardPolicy:直接丢弃
- DiscardOldestPolicy:丢弃最老的任务
15. 消息队列如何保证消息不丢失?(以RabbitMQ为例)
三个环节:
1. 生产者->MQ:发送确认
- 开启confirm机制,收到ack才算成功
- 失败重试或记录日志
2. MQ自身:持久化
- 交换机、队列、消息都设置持久化
- durable=true
3. MQ->消费者:手动ack
- 关闭自动ack
- 业务处理成功后手动确认
- 失败后requeue或放入死信队列
额外保障:
- 消费端幂等性处理(防止重复消费)
- 消息表记录状态,定时任务补偿
🎨 React前端篇(15题)
16. React的虚拟DOM和Diff算法?
虚拟DOM:用JS对象描述真实DOM树,对比差异后批量更新
Diff算法三大策略:
- Tree Diff:只比较同层节点,不跨层级
- Component Diff:同类型组件才对比Virtual DOM,不同类型直接替换
- Element Diff:同层元素通过key识别,移动而非重建
key的作用:
- 唯一标识,帮助React识别哪些元素改变
- 不要用index作为key(顺序变化会导致全部重新渲染)
React 18的并发渲染:
- Fiber架构:可中断的渲染
- 时间切片:把渲染任务拆分,不阻塞主线程
17. React Hooks为什么不能在条件语句中使用?
原因:Hooks依赖调用顺序来维护状态
React内部用链表存储Hooks,每次渲染按顺序调用:
- 第一个useState对应链表第一个节点
- 第二个useEffect对应第二个节点
如果在条件语句中使用:
// ❌ 错误示例 if (condition) { useState(0); // 可能不执行,打乱顺序 }链表顺序错乱,导致状态错乱
规则:
- 只在顶层调用Hooks
- 只在React函数中调用Hooks
18. useState和useReducer的区别?
useState:
- 适合简单状态
- 直接更新值
const [count, setCount] = useState(0); setCount(count + 1);useReducer:
- 适合复杂状态逻辑
- 通过dispatch(action)更新
- 类似Redux,可预测的状态管理
const [state, dispatch] = useReducer(reducer, initialState); dispatch({ type: 'increment' });使用场景:
- 多个子值组成的复杂对象:useReducer
- 下一个状态依赖前一个:useReducer
- 简单的独立值:useState
19. useEffect和useLayoutEffect的区别?
执行时机:
- useEffect:异步执行,DOM更新后、浏览器绘制后
- useLayoutEffect:同步执行,DOM更新后、浏览器绘制前
使用场景:
- useEffect:99%的场景,数据获取、订阅、副作用
- useLayoutEffect:需要同步测量DOM、避免闪烁(如动画、滚动位置)
例子:
// 会闪烁 useEffect(() => { ref.current.style.top = '100px'; // 先渲染再改样式 }, []); // 不会闪烁 useLayoutEffect(() => { ref.current.style.top = '100px'; // 渲染前就改样式 }, []);20. React性能优化手段?
1. 避免不必要的渲染:
- React.memo:函数组件浅比较props
- useMemo:缓存计算结果
- useCallback:缓存函数引用
- PureComponent:类组件浅比较props和state
2. 代码分割:
- React.lazy + Suspense:路由懒加载
- 动态import()
3. 虚拟列表:
- react-window、react-virtualized
- 只渲染可见区域
4. 合理使用key:
- 列表渲染必须有唯一key
- 不用index作key
5. 避免内联对象和函数:
// ❌ 每次渲染都创建新对象 <Child style={ { color: 'red' }} /> // ✅ 提取到外部 const style = { color: 'red' }; <Child style={style} />21. React的状态管理方案对比?
1. Redux:
- 优点:可预测、时间旅行、中间件生态
- 缺点:样板代码多、学习曲线陡
- 适合:大型应用、复杂状态
2. Zustand:
- 优点:API简洁、无Provider包裹、性能好
- 适合:中小型应用
3. Mobx:
- 优点:响应式、代码少
- 缺点:不够明确、调试困难
- 适合:快速开发
4. Context + useReducer:
- 优点:原生、无需引入库
- 缺点:性能问题(Provider的value变化所有消费者重渲染)
- 适合:轻量级状态共享
5. Redux Toolkit(推荐):
- Redux的现代封装
- 简化Redux代码
- 内置immer、thunk
22. 闭包陷阱及解决方案?
问题:
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(count + 1); // count永远是0,闭包陷阱! }, 1000); return () => clearInterval(timer); }, []); // 依赖数组为空,只执行一次 }解决方案:
方法1:使用函数式更新
setCount(prevCount => prevCount + 1);方法2:添加依赖
useEffect(() => { const timer = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(timer); }, [count]); // 每次count变化重新创建定时器方法3:使用useRef
const countRef = useRef(count); countRef.current = count;23. React Router的原理?
两种模式:
1. Hash模式:
- URL:
http://example.com/#/home - 监听:
window.addEventListener('hashchange', fn) - 特点:兼容性好,但URL有#号
2. History模式(主流):
- URL:
http://example.com/home - API:
history.pushState()、history.replaceState() - 监听:
window.addEventListener('popstate', fn) - 注意:需要服务端配置,否则刷新404
实现原理:
- 通过Context传递路由信息
- 监听URL变化
- 匹配路由组件
- 渲染对应组件
24. React事件机制(合成事件)?
特点: