Java面试突击版!快速拿下offer的神技!面试题分享!
Java 面试突击核心知识点与高频题
一、Java 基础
1. == 和 equals 的区别
- ==:对基本类型比较值,对引用类型比较内存地址。
- equals():默认等价于 ==(Object 类实现),但 String、Integer 等类重写后用于比较内容。
2. String、StringBuilder、StringBuffer 的区别
- String:不可变字符序列,线程安全,性能低(修改会创建新对象)。
- StringBuilder:可变字符序列,非线程安全,性能高(适合单线程)。
- StringBuffer:可变字符序列,线程安全(方法加 synchronized),性能略低(适合多线程)。
二、集合框架
1. HashMap 的底层原理(JDK 1.8)
- 数据结构:数组 + 链表 / 红黑树(链表长度 > 8 且数组容量 ≥ 64 时转为红黑树)。
- 哈希寻址:通过
hash(key) & (length - 1)计算数组索引。 - 扩容机制:默认容量 16,负载因子 0.75,扩容时容量翻倍,重新计算索引。
2. ArrayList 和 LinkedList 的区别
- ArrayList:基于动态数组,随机访问快(O (1)),插入 / 删除慢(需移动元素)。
- LinkedList:基于双向链表,随机访问慢(O (n)),插入 / 删除快(只需修改指针)。
三、多线程与并发
1. synchronized 和 Lock 的区别
- synchronized:Java 关键字,自动加锁 / 解锁,可修饰方法或代码块,不可中断。
- Lock:接口(如 ReentrantLock),需手动加锁 / 解锁,支持中断、超时、公平锁等高级功能。
2. 线程池的核心参数(ThreadPoolExecutor)
- corePoolSize:核心线程数(即使空闲也保留)。
- maximumPoolSize:最大线程数。
- keepAliveTime:非核心线程空闲存活时间。
- workQueue:任务队列(如 LinkedBlockingQueue)。
- handler:拒绝策略(如 AbortPolicy、CallerRunsPolicy)。
四、JVM
1. JVM 内存结构(运行时数据区)
- 程序计数器:记录当前线程执行的字节码行号,线程私有。
- Java 虚拟机栈:存储局部变量表、操作数栈等,线程私有,方法调用时创建栈帧。
- 本地方法栈:为 Native 方法服务,线程私有。
- 堆:存放对象实例,线程共享,是 GC 的主要区域(分为年轻代、老年代)。
- 方法区:存储类信息、常量、静态变量等,线程共享(JDK 1.8 后用元空间实现)。
2. 常见 GC 算法
- 标记 - 清除:先标记垃圾,再统一清除,会产生内存碎片。
- 复制:将存活对象复制到新区域,清空原区域,无碎片但需额外空间。
- 标记 - 整理:标记存活对象后,将其向一端移动并清空边界外区域,无碎片但移动成本高。
五、Spring/Spring Boot
1. IOC(控制反转)和 AOP(面向切面编程)
- IOC:将对象的创建和依赖关系的管理交给 Spring 容器,降低耦合。
- AOP:通过动态代理(JDK 动态代理或 CGLIB)将横切逻辑(如日志、事务)与业务逻辑分离。
2. Spring Boot 的自动配置原理
- 核心注解
@SpringBootApplication包含@EnableAutoConfiguration,通过@Import加载AutoConfigurationImportSelector。 - 读取
META-INF/spring.factories文件中的自动配置类,根据条件注解(如@ConditionalOnClass)按需加载。
六、数据库
1. 索引的作用及常见类型
- 作用:提高查询效率(减少数据扫描量),但会增加插入 / 删除 / 更新的成本。
- 常见类型:B + 树索引(InnoDB 默认)、哈希索引、全文索引等。
2. 事务的 ACID 特性
- 原子性(Atomicity):事务是不可分割的最小单位,要么全成功,要么全失败。
- 一致性(Consistency):事务执行前后,数据从一个一致状态到另一个一致状态。
- 隔离性(Isolation):并发事务之间互不干扰(通过隔离级别控制)。
- 持久性(Durability):事务提交后,数据永久保存到数据库。
面试小技巧
- 项目介绍用 STAR 法则:S(背景)→ T(任务)→ A(行动)→ R(结果),突出技术难点和你的贡献。
- 遇到不会的问题:诚实说明,但可展示思路(如 “这个我暂时没深入研究,但我会从 XX 角度去分析”)。
- 主动引导:回答时适当提及自己熟悉的技术点,引导面试官追问。
并发编程模块超详细面试题解析
并发编程是 Java 面试中必考且占比极高的模块,以下是 6 道核心高频题的深度解析,覆盖原理、底层实现与实战场景。
1. 详细说一下synchronized的底层实现原理?
synchronized是 JVM 内置的锁,通过对象头和 Monitor(管程)实现,JDK 1.6 后引入锁升级机制优化性能。
核心解析:
- 对象头结构:每个 Java 对象的对象头包含
Mark Word(标记字段)和Class Pointer(类型指针)。Mark Word存储对象的哈希码、分代年龄、锁状态等信息。 - 锁升级流程:
- 无锁:对象刚创建,未被任何线程锁定。
- 偏向锁:当第一个线程访问锁时,
Mark Word记录该线程 ID,后续该线程无需 CAS 操作直接获取锁(适用于单线程重复加锁场景)。 - 轻量级锁:当有第二个线程竞争锁时,偏向锁升级为轻量级锁。线程在栈帧中创建
Lock Record,通过 CAS 尝试将对象头的Mark Word替换为指向Lock Record的指针,成功则获取锁;失败则自旋等待(避免线程切换开销)。 - 重量级锁:若自旋超过一定次数(JDK 1.7 后自适应自旋),轻量级锁升级为重量级锁,此时线程阻塞,由操作系统调度(依赖
monitorenter和monitorexit指令,底层通过操作系统的互斥量实现)。
2.volatile关键字的作用是什么?底层如何实现?
volatile是 JVM 提供的轻量级同步机制,保证可见性和禁止指令重排序,但不保证原子性。
核心解析:
- 可见性:当一个线程修改了
volatile变量的值,其他线程能立即看到最新值(基于 JMM 的主内存与工作内存模型)。底层实现:通过内存屏障(Memory Barrier)实现。写volatile变量时,插入StoreStore屏障(禁止前面的写与后面的写重排序)+StoreLoad屏障(禁止写与后面的读重排序),并将变量值刷新回主内存;读volatile变量时,插入LoadLoad屏障(禁止前面的读与后面的读重排序)+LoadStore屏障(禁止读与后面的写重排序),并从主内存重新读取变量。 - 禁止指令重排序:JVM 会对指令进行重排序优化,但
volatile变量通过内存屏障限制重排序,保证执行顺序与代码逻辑一致(如单例模式的双重校验锁中,volatile防止对象初始化指令重排序)。
3.ThreadLocal是什么?底层原理?内存泄漏问题如何解决?
ThreadLocal是线程本地变量,每个线程都有独立的变量副本,实现线程间数据隔离。
核心解析:
- 底层结构:每个
Thread对象内部维护一个ThreadLocalMap(ThreadLocal的静态内部类),ThreadLocalMap以ThreadLocal实例为弱引用(WeakReference)作为 key,用户存储的变量为 value。 - 内存泄漏原因:
ThreadLocalMap的 key 是弱引用,当ThreadLocal实例被回收(无强引用指向),key 变为 null,但 value 仍被强引用(Thread→ThreadLocalMap→ Entry → value),若线程不结束,value 永远无法被 GC,导致内存泄漏。 - 解决方案:使用完
ThreadLocal后,手动调用remove()方法,删除对应的 Entry,避免内存泄漏。
4. 线程池ThreadPoolExecutor的执行流程?核心参数如何设置?
线程池通过复用线程减少创建 / 销毁开销,ThreadPoolExecutor是 Java 原生线程池的核心实现。
核心解析:
- 执行流程:
- 提交任务时,若当前运行线程数 <
corePoolSize(核心线程数),创建核心线程执行任务。 - 若核心线程数已满,将任务加入
workQueue(任务队列)。 - 若队列已满,且当前运行线程数 <
maximumPoolSize(最大线程数),创建非核心线程执行任务。 - 若线程数已达最大值,执行
RejectedExecutionHandler(拒绝策略)。
- 提交任务时,若当前运行线程数 <
- 核心参数设置建议:
- CPU 密集型任务:
corePoolSize= CPU 核心数 + 1(减少线程切换开销),maximumPoolSize= CPU 核心数 + 1。 - IO 密集型任务:
corePoolSize= 2 × CPU 核心数(IO 等待时可切换线程执行其他任务),maximumPoolSize可根据实际情况适当增大。 - 任务队列:建议用
LinkedBlockingQueue(有界,避免 OOM),不建议用无界队列(如newFixedThreadPool的默认队列)。
- CPU 密集型任务:
5. AQS(AbstractQueuedSynchronizer)的核心原理是什么?
AQS 是构建锁和同步器的框架(如ReentrantLock、CountDownLatch都基于 AQS),核心是通过state 变量和CLH 队列实现同步。
核心解析:
- state 变量:用
volatile int state表示同步状态(如ReentrantLock中 state=0 表示无锁,state>0 表示重入次数)。AQS 提供getState()、setState()、compareAndSetState()方法操作 state。 - CLH 队列:虚拟的双向队列(无实际队列结构,仅维护节点间的引用关系),用于存储等待锁的线程。当线程获取锁失败时,会被封装成
Node节点加入队列尾部,并通过自旋或LockSupport.park()阻塞;当锁释放时,唤醒队列头节点的线程。 - 两种模式:
- 独占模式:同一时间只有一个线程能获取锁(如
ReentrantLock)。 - 共享模式:同一时间多个线程可获取锁(如
CountDownLatch、Semaphore)。
- 独占模式:同一时间只有一个线程能获取锁(如
6. CAS 是什么?ABA 问题如何解决?
CAS(Compare-And-Swap,比较并交换)是一种无锁算法,通过硬件指令保证原子性,避免线程切换开销。
核心解析:
- CAS 原理:包含三个操作数 —— 内存地址 V、预期值 A、新值 B。当且仅当 V 的值等于 A 时,将 V 的值更新为 B;否则不做任何操作,重试(自旋)。底层实现:JDK 通过
Unsafe类的compareAndSwapInt()、compareAndSwapLong()等方法调用 CPU 的cmpxchg指令(X86 架构)实现原子性。 - ABA 问题:若变量 V 的值从 A 变为 B,再变回 A,CAS 会误以为值未变化,导致逻辑错误(如栈操作中,节点被替换后又恢复,可能导致数据丢失)。
- 解决方案:使用版本号或时间戳。JDK 提供
AtomicStampedReference类,通过维护 “变量值 + 版本号” 的方式,每次更新时同时更新版本号,只有值和版本号都匹配时才更新,避免 ABA 问题。
Java面试题分享
需要拿来学习的同学查看下方名片!
