Java核心基础深度解析
一、面向对象与基本类型
在Java面试基础知识总结中,面向对象(OOP)是永恒的基石。面试官常问:“为什么Java只有值传递?”这需要从内存模型角度解释。对于基本数据类型(int, double等),传递的是值的副本;对于引用数据类型(Object, Array等),传递的是对象在堆内存中地址的副本。理解这一点至关重要。
1.1 equals() 与 == 的区别
== 对于基本类型比较的是值,对于引用类型比较的是内存地址。而 equals() 默认情况下(在Object类中)也是比较地址,但String等类重写了该方法,用于比较内容。在面试中,建议展示如何正确重写 equals() 和 hashCode(),遵循契约:如果两个对象相等,它们的哈希码必须相等。
1.2 String, StringBuilder 与 StringBuffer
这是高频考点。String是不可变的(final),任何修改都会产生新对象,适合少量字符串操作。StringBuffer是线程安全的,方法同步,但性能较低。StringBuilder是非线程安全的,性能最高,适合单线程下的字符串拼接。在循环拼接字符串时,务必使用StringBuilder。
二、异常处理机制
Java异常分为检查型异常(Checked Exception)和非检查型异常(RuntimeException)。面试中常问:“如何设计良好的异常处理?” 答案包括:避免捕获过于宽泛的Exception,尽量使用特定的异常类型;在finally块中释放资源(或使用try-with-resources);不要吞掉异常,要有日志记录或向上抛出。
集合框架源码剖析
ArrayList 核心机制
ArrayList基于动态数组实现,默认初始容量为10。当容量不足时,会扩容为原来的1.5倍(oldCapacity + (oldCapacity >> 1))。
- ⚡ 优点:支持随机访问,查询速度快 O(1)。
- ⚠️ 缺点:插入和删除元素需要移动大量元素,效率低 O(n)。
- 📌 面试点:为什么ArrayList扩容是1.5倍而不是2倍?1.5倍是经验值,平衡了内存浪费和扩容频率。
HashMap 数据结构与原理
HashMap在JDK1.7中是数组+链表,在JDK1.8中优化为数组+链表+红黑树。当链表长度超过8且数组长度超过64时,链表转化为红黑树,将查找复杂度从 O(n) 降低到 O(log n)。
- ⚡ 哈希冲突解决:链地址法。
- ⚙️ 扩容机制:阈值 = 容量 负载因子(默认0.75)。扩容后元素位置要么在原位,要么在原位+旧容量。
- ⚠️ 线程不安全:多线程put可能导致死循环(1.7)或数据覆盖(1.8)。
ConcurrentHashMap 高并发实现
JDK1.7采用Segment分段锁,1.8采用CAS+synchronized锁粒度细化到桶节点。
- ⚡ CAS:Compare And Swap,无锁乐观并发控制。
- ⚙️ Synchronized:仅锁住当前桶的头节点,并发度极高。
- 📌 size()方法:通过累加CounterCell数组,避免全局锁。
JVM内存模型与垃圾回收
一、运行时数据区
在Java面试基础知识总结中,JVM内存模型是区分初级与高级开发者的关键。JVM内存主要分为:
- 堆(Heap):存储对象实例,是GC的主要区域。分为年轻代(Eden, Survivor0, Survivor1)和老年代。
- 虚拟机栈(VM Stack):存储栈帧,局部变量表、操作数栈等。每个线程私有。
- 本地方法栈(Native Method Stack):为Native方法服务。
- 方法区(Method Area)/ 元空间(Metaspace):存储类信息、常量、静态变量。JDK8后使用元空间,位于本地内存。
- 程序计数器(Program Counter Register):当前线程执行的字节码行号指示器。
二、垃圾回收算法
| 算法 | 原理 | 优缺点 |
|---|---|---|
| 标记-清除 | 先标记所有需要回收的对象,再清除。 | 简单,但产生内存碎片。 |
| 复制算法 | 将内存分为两块,每次用一块,回收时把存活对象复制到另一块。 | 无碎片,效率高,但浪费一半内存(用于年轻代)。 |
| 标记-整理 | 标记后让存活对象向一端移动,然后清理边界外内存。 | 无碎片,适合老年代。 |
三、常用GC收集器
面试常问CMS与G1的区别。CMS(Concurrent Mark Sweep)注重停顿时间,但容易产生碎片。G1(Garbage First)将堆划分为多个区域,可预测停顿时间,收集整块堆内存,是目前主流的选择。ZGC则致力于亚毫秒级的停顿时间。
Java并发编程实战
一、线程状态与生命周期
Java线程有6种状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED。理解状态转换是解决线程问题的基础。
二、 synchronized 与 ReentrantLock
synchronized
⚙️ 关键字,JVM层面实现。自动加锁解锁。
⚡ 不可中断,非公平锁。
📌 优化:锁升级(无锁->偏向锁->轻量级锁->重量级锁)。
ReentrantLock
⚙️ 类,API层面实现(lock/unlock)。
⚡ 可中断,可公平/非公平,支持Condition。
📌 必须手动释放锁,通常在finally块中。
三、线程池参数与工作流程
创建线程池必须使用 ThreadPoolExecutor,避免使用Executors工具类(可能导致OOM)。核心参数包括:
corePoolSize: 核心线程数 maximumPoolSize: 最大线程数 keepAliveTime: 空闲线程存活时间 unit: 时间单位 workQueue: 任务队列 threadFactory: 线程工厂 handler: 拒绝策略
工作流程:新任务提交 -> 核心线程满 -> 进入队列 -> 队列满 -> 创建非核心线程 -> 非核心线程满 -> 执行拒绝策略(Abort/Discard/CallerRuns等)。
Spring生态与主流框架
一、Spring IoC 与 AOP
IoC(控制反转):将对象的生命周期管理交给Spring容器,通过DI(依赖注入)实现解耦。常见的注入方式有构造器注入、Setter注入、字段注入(不推荐)。
AOP(面向切面编程):将通用逻辑(如事务、日志、权限)从业务逻辑中分离。核心概念:切面(Aspect)、连接点(JoinPoint)、切点(Pointcut)、通知(Advice)。
二、Spring Bean 生命周期
这是一个经典面试题。流程如下:
- 实例化 Bean(Instantiation)
- 属性赋值(Populate)
- 初始化(Initialization):
- 检查 Aware 接口(BeanName, BeanFactory等)
- 执行 BeanPostProcessor.postProcessBeforeInitialization
- 执行 init-method 或 @PostConstruct
- 执行 BeanPostProcessor.postProcessAfterInitialization
- 使用 Bean
- 销毁(Destruction):执行 destroy-method 或 @PreDestroy
三、MyBatis 缓存机制
MyBatis有一级缓存(SqlSession级别,默认开启)和二级缓存(Mapper级别,需手动开启)。一级缓存基于PerpetualCache(HashMap),二级缓存基于自定义Cache接口。注意:在分布式环境下,二级缓存可能导致数据不一致,通常建议使用Redis等外部缓存。
数据库MySQL与Redis
一、MySQL索引优化
索引底层主要使用B+树。为什么不用B树或Hash?B树多路搜索,高度低但查询不稳定;Hash不支持范围查询。B+树非叶子节点只存索引,叶子节点存数据并链表连接,适合范围查询。
覆盖索引:查询的列恰好是索引列,无需回表。最左前缀原则:联合索引 (a,b,c),查询条件必须包含a才能生效。
二、Redis 数据结构与应用
Redis不仅是Key-Value存储,还提供String, List, Set, ZSet, Hash等结构。
- ⚡ 持久化:RDB(快照,速度快,易丢失) vs AOF(日志,数据全,文件大)。
- ⚙️ 过期策略:惰性删除 + 定期删除。
- 📌 缓存穿透/击穿/雪崩:
- 穿透:查不存在的数据 -> 布隆过滤器/缓存空值。
- 击穿:热点Key过期 -> 互斥锁/逻辑过期。
- 雪崩:大量Key同时过期 -> 随机 TTL/集群架构。
分布式与微服务架构
一、分布式锁
在微服务架构中,如何实现分布式锁?
- 基于Redis:使用
SETNX+EXPIRE(或Lua脚本保证原子性)。推荐Redisson,支持看门狗机制自动续期。 - 基于Zookeeper:利用临时顺序节点。获取锁时创建临时节点,监听前一个节点删除事件。适合强一致性场景。
二、消息队列(Kafka/RocketMQ)
MQ主要用于:解耦、异步、削峰。
Kafka:高吞吐,日志系统,适合大数据实时计算。顺序写磁盘,零拷贝技术。
RocketMQ:阿里出品,事务消息支持好,适合金融级业务。支持延时消息。
消息可靠性:生产者确认机制 + MQ持久化 + 消费者手动ACK。