一、Java基础篇
1.问:==和equals()有什么区别?
答:
==:比较的是内存地址(对于基本类型,比较的是值;对于引用类型,比较的是是否指向同一个对象)。equals():是Object类的方法。默认行为与==一样(比较地址)。但String、Integer等类重写了它,用于比较内容是否相同。
加分回答:重写equals()时,必须同时重写hashCode(),保证两个相等对象拥有相同哈希码,否则在HashMap等集合中会出问题。
2.问:String、StringBuffer、StringBuilder 有什么区别?
答:
| 类型 | 是否可变 | 线程安全 | 适用场景 |
|---|---|---|---|
| String | 不可变(每次操作产生新对象) | 安全 | 少量字符串操作 |
| StringBuffer | 可变 | 安全(方法加synchronized) | 多线程下的频繁修改 |
| StringBuilder | 可变 | 不安全 | 单线程下的频繁修改(性能最高) |
3.问:接口和抽象类的区别?
| 维度 | 抽象类 | 接口(Java 8+) |
|---|---|---|
| 关键字 | abstract class | interface |
| 构造方法 | 可以有 | 不能有 |
| 字段 | 可以是各种访问修饰符 | 默认 public static final |
| 普通方法 | 可以有具体实现 | Java 8 后可用 default/static 方法实现 |
| 多继承 | 单继承 | 可多实现 |
核心原则:用抽象类表达“is-a”关系,用接口表达“can-do”能力。
二、集合框架篇
4.问:HashMap 的底层原理是什么?(高频)
答:
JDK 1.7:数组 + 链表
JDK 1.8+:数组 + 链表 + 红黑树
通过
hash(key)定位数组下标冲突时链表存储
链表长度 >8 且数组长度 ≥64 时,链表转红黑树,提高查询效率(O(logn))
当元素数量 > 容量*负载因子(默认0.75)时,扩容翻倍,所有元素重新哈希(rehash)
关键点:hashCode()决定存储位置,equals()判断是否相同。
5.问:HashMap 和 Hashtable 的区别?
| 对比点 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 否(允许并发) | 是(方法加 synchronized) |
| key/value 能否为 null | key和value可以为null | 不允许 null |
| 性能 | 高 | 低 |
| 父类 | AbstractMap | Dictionary |
替代方案:需要线程安全时用ConcurrentHashMap。
6.问:ArrayList 与 LinkedList 怎么选?
ArrayList:底层数组,随机访问快(O(1)),中间插入/删除慢(需移动元素)。
LinkedList:底层双向链表,插入/删除快(O(1)),随机访问慢(需遍历)。
一句话选型:查询多用ArrayList,增删多用LinkedList。
三、并发编程篇
7.问:synchronized 和 Lock 有什么区别?
| 对比点 | synchronized | Lock(如 ReentrantLock) |
|---|---|---|
| 使用方式 | 关键字,自动释放锁 | API,需手动 unlock() |
| 可中断 | 不支持 | 支持 lockInterruptibly() |
| 超时获取 | 不支持 | 支持 tryLock(timeout) |
| 公平锁 | 非公平 | 可公平/非公平 |
| 性能 | 早期差,现在优化后接近 | 两者差不多 |
建议:简单同步用 synchronized;需要高级功能(可中断、超时、公平锁)用 Lock。
8.问:volatile 能保证原子性吗?
不能。
volatile保证:可见性:一个线程修改后,其他线程立即可见。
禁止指令重排序。
不保证原子性:例如
count++不是原子操作(读-改-写三步),多线程下仍会出错。
解决方案:用AtomicInteger或synchronized。
9.问:线程池的核心参数有哪些?
java
new ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, RejectedExecutionHandler handler // 拒绝策略 )
工作流程:
线程数 < corePoolSize → 新建线程
线程数 ≥ corePoolSize → 任务入队列
队列满 & 线程数 < maximumPoolSize → 新建非核心线程
队列满 & 线程数 = maximumPoolSize → 拒绝策略
四、JVM篇(大厂必问)
10.问:JVM 内存模型(运行时数据区)有哪些?
堆(Heap):存储对象实例,线程共享,GC 主要区域。
栈(VM Stack):存储局部变量、方法调用,线程私有。
方法区(Method Area):存储类信息、常量、静态变量;Java 8 后改为元空间(Metaspace),使用本地内存。
程序计数器(PC Register):当前线程执行的字节码行号。
本地方法栈(Native Method Stack):为 native 方法服务。
11.问:垃圾回收(GC)如何判断对象可回收?
引用计数法:循环引用问题无法解决(已淘汰)。
可达性分析(主流):从 GC Roots 出发,不可达的对象可回收。
GC Roots 包括:
栈帧中的局部变量引用的对象
静态属性引用的对象
常量池中的引用
JNI(Native方法)引用的对象
12.问:有哪些常见的垃圾回收器?
Serial:单线程,年轻代,适合客户端。
Parallel Scavenge:多线程,关注吞吐量(服务器默认)。
CMS:并发收集,低停顿(老年代),但会产生碎片。
G1:兼顾吞吐量和低停顿,替代 CMS,将堆分成 Region。
ZGC(JDK 11+):极低停顿(<10ms),超大堆(TB级)。
五、Java 新特性篇
13.问:Java 8 的 Stream 用过吗?举个例子。
Stream 用于对集合进行函数式操作(链式处理)。
示例:筛选金额大于1000的订单,并排序取出前5个订单号
java
List<Integer> top5Ids = orders.stream() .filter(o -> o.getAmount() > 1000) .sorted(Comparator.comparing(Order::getAmount).reversed()) .map(Order::getId) .limit(5) .collect(Collectors.toList());
特性:不修改源数据,延迟执行(终止操作才执行)。
14.问:Java 17 较重要的特性?
密封类(Sealed Classes):限制子类,
sealed interface Shape permits Circle, Rect {}文本块:
""" multi-line string """switch 表达式增强:可返回值,箭头语法,支持模式匹配
六、附加:高频进阶题
15.问:类加载过程?
加载:找到类的字节码,创建 Class 对象。
验证:校验字节码安全性。
准备:为静态变量分配内存并设默认值。
解析:把符号引用转为直接引用。
初始化:执行静态代码块和静态变量赋值。
双亲委派模型:类加载请求先委派给父加载器,父类无法加载才由子类尝试。避免重复加载和核心类被篡改。
16.问:什么情况会导致内存泄漏?
长生命周期对象持有短对象引用(如静态集合)。
未关闭的资源(连接、流)。
监听器/回调未注销。
ThreadLocal使用完未remove()。
面试小结
以上问题基本覆盖 Java 面试的核心高频区域。想要表现更好,建议:
不要背答案:先理解原理,再组织自己的话。
主动扩展:比如被问到 HashMap,可以主动扩展到 ConcurrentHashMap、红黑树、哈希冲突的解决。
结合项目:JVM调优、线程池参数选择等,举例说明你实际用过。
最后送上一句心法:
面试官问“是什么”检验基础,问“为什么”考验深度,问“怎么优化”考察经验。
祝你面试顺利!如果有具体想深入了解的点(比如某个JVM调优案例或并发工具源码),可以告诉我,我可以再为你细化。