1. Java基础面试题精选
1.1 Java中的值传递与引用传递
在Java面试中,"Java是值传递还是引用传递"这个问题出现的频率高达90%。很多初级开发者容易混淆这个概念,我们先来看个典型例子:
java复制public class PassByValueExample {
public static void main(String[] args) {
Dog myDog = new Dog("Rover");
foo(myDog);
System.out.println(myDog.getName()); // 输出什么?
}
public static void foo(Dog someDog) {
someDog.setName("Max");
someDog = new Dog("Fifi");
}
}
这里的关键理解点是:
- Java中所有参数传递都是值传递
- 对象引用也是按值传递(即传递的是引用的副本)
- 方法内部可以修改对象状态,但不能让原始引用指向新对象
实际开发中常见的误区:
- 认为基本类型是值传递,对象是引用传递(错误)
- 不理解方法内重新赋值为何不影响外部引用
- 混淆对象状态修改和引用变更的区别
提示:可以用"遥控器与电视机"的比喻来理解 - 传递的是遥控器的副本,你可以用副本遥控器换台(修改对象状态),但不能用副本遥控器替换原遥控器(改变引用指向)
1.2 String的不可变性设计
String的不可变性是Java设计的精妙之处,面试常问的设计考量:
java复制String s1 = "Java";
String s2 = "Java";
String s3 = new String("Java");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
背后的设计原理:
- 字符串常量池优化:重复字面量共享同一内存
- 安全性:作为参数传递时不会被意外修改
- 线程安全:天然适合多线程环境
- HashCode缓存:因为不可变,hashCode只需计算一次
实际应用中的注意事项:
- 大量字符串拼接用StringBuilder
- 敏感信息处理完应立即清空(因为String不可变,会长期驻留内存)
- 注意intern()方法的使用场景
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 集合框架深度解析
2.1 HashMap的底层实现原理
HashMap是使用最频繁的集合类,也是面试必问点。JDK8后的重要改进:
java复制// 典型put过程伪代码
final V putVal(int hash, K key, V value) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 1. 表为空则初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 2. 计算桶位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 3. 处理哈希冲突
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 4. 链表处理
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 5. 值替换
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 6. 扩容检查
if (++size > threshold)
resize();
return null;
}
关键知识点:
- 数组+链表+红黑树结构(链表长度>8且数组长度≥64时转红黑树)
- 扰动函数:(h = key.hashCode()) ^ (h >>> 16)
- 负载因子默认0.75(时间与空间的trade-off)
- 扩容时rehash的优化:新位置=原位置或原位置+旧容量
实际开发中的经验:
- 初始化时设置合理容量(避免频繁扩容)
- 复杂对象作为key时要同时重写hashCode()和equals()
- 多线程环境用ConcurrentHashMap
2.2 ArrayList与LinkedList对比
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 头部插入 | O(n) | O(1) |
| 内存占用 | 更小(仅需存储元素) | 更大(需要节点对象) |
| 迭代性能 | 更快(连续内存) | 较慢(指针跳转) |
| 适用场景 | 读多写少,随机访问频繁 | 频繁在首尾增删元素 |
实际选型建议:
- 80%场景ArrayList是更好选择
- 实现队列功能时用LinkedList
- 超大数据量考虑使用Guava的ImmutableList
3. 并发编程核心问题
3.1 synchronized的实现原理
synchronized的底层实现经历了多次优化:
java复制public class SynchronizedExample {
// 1. 同步代码块
public void method1() {
synchronized (this) {
// 临界区
}
}
// 2. 同步方法
public synchronized void method2() {
// 临界区
}
// 3. 静态同步方法
public static synchronized void method3() {
// 临界区
}
}
JVM层面的实现机制:
- 代码块同步:使用monitorenter和monitorexit指令
- 方法同步:通过方法访问标志的ACC_SYNCHRONIZED标识
- 锁升级过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
优化建议:
- 减小同步块范围
- 避免在循环内同步
- 不同功能使用不同的锁对象
- 考虑使用ReentrantLock的tryLock()避免死锁
3.2 volatile关键字的作用
volatile的典型应用场景:
java复制class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
// do something
}
}
}
volatile的三大特性:
- 可见性:写操作会立即刷新到主内存
- 禁止指令重排序:通过内存屏障实现
- 不保证原子性(如i++操作仍需同步)
实际应用注意事项:
- 状态标志位是最佳使用场景
- 单例模式的双重检查锁定中必须使用
- 不要用于复杂的原子性操作
4. JVM内存模型与GC
4.1 内存区域划分
JVM内存结构示意图(以JDK8为例):
code复制┌───────────────────────┐
│ JVM Memory │
├───────────┬───────────┤
│ Thread │ Shared │
│ Private │ Memory │
├─┬─┬───────┼─┬─┬───────┤
│P│J│ │H│M│ │
│C│V│ │e│e│ │
│ │M│ │a│t│ │
│ │S│ │p│a│ │
│ │ │ │ │s│ │
│ │ │ │ │p│ │
│ │ │ │ │a│ │
│ │ │ │ │c│ │
│ │ │ │ │e│ │
└─┴─┴───────┴─┴─┴───────┘
关键区域说明:
- 程序计数器:线程私有,唯一不会OOM的区域
- 虚拟机栈:存储栈帧,包含局部变量表等
- 本地方法栈:为Native方法服务
- 堆:对象实例存储区域,GC主要工作区
- 方法区:存储类信息、常量等(JDK8后是元空间)
常见内存问题:
- 堆溢出:OutOfMemoryError: Java heap space
- 栈溢出:StackOverflowError
- 元空间溢出:OutOfMemoryError: Metaspace
4.2 GC算法与垃圾收集器
主流垃圾收集器对比:
| 收集器 | 分代 | 算法 | 线程 | 适用场景 |
|---|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程 | 客户端模式 |
| Parallel Scavenge | 新生代 | 复制 | 多线程 | 吞吐量优先 |
| ParNew | 新生代 | 复制 | 多线程 | 与CMS配合 |
| CMS | 老年代 | 标记-清除 | 多线程 | 低延迟 |
| G1 | 全堆 | 标记-整理+分区 | 多线程 | 大堆内存,平衡吞吐与延迟 |
| ZGC | 全堆 | 着色指针+读屏障 | 多线程 | 超大堆(TB级),超低延迟 |
调优建议:
- 优先调整堆大小(-Xms, -Xmx)
- 关注GC日志(-XX:+PrintGCDetails)
- 根据应用特性选择收集器:
- Web应用:G1或CMS
- 批处理:Parallel Scavenge + Parallel Old
- 避免频繁Full GC
5. 设计模式实战应用
5.1 单例模式的正确实现
线程安全的单例实现演进:
java复制// 1. 饿汉式(推荐)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
// 2. 双重检查锁(JDK5+)
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 3. 静态内部类(推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
// 4. 枚举式(最安全)
public enum Singleton {
INSTANCE;
public void doSomething() {
// ...
}
}
选择建议:
- 简单场景用饿汉式
- 明确需要懒加载用静态内部类
- 需要防反射攻击用枚举式
- 避免使用双重检查锁(容易写错)
5.2 Spring中的设计模式应用
Spring框架中典型的设计模式应用:
- 工厂模式:BeanFactory
- 代理模式:AOP实现
- 模板方法:JdbcTemplate
- 观察者模式:ApplicationEvent
- 适配器模式:HandlerAdapter
- 装饰器模式:Wrapper类
- 策略模式:Resource接口
面试中常问的Spring设计模式问题:
- BeanFactory和FactoryBean的区别
- AOP的动态代理实现选择(JDK vs CGLIB)
- 模板方法在事务管理中的应用
- Spring事件机制的实际应用场景
6. 异常处理最佳实践
6.1 异常体系结构
Java异常类层次结构:
code复制Throwable
├── Error
│ ├── VirtualMachineError
│ └── ...
└── Exception
├── RuntimeException
│ ├── NullPointerException
│ └── ...
└── 非RuntimeException
├── IOException
└── ...
异常处理原则:
- 不要捕获Throwable或Error
- 受检异常与非受检异常区分处理
- 避免空的catch块
- 优先使用标准异常
- 异常包含足够上下文信息
6.2 异常性能优化
异常处理的性能陷阱:
java复制// 反模式 - 使用异常控制流程
try {
while(true) {
list.get(index++);
}
} catch (IndexOutOfBoundsException e) {
// 结束循环
}
// 正确做法
while(index < list.size()) {
list.get(index++);
}
优化建议:
- 创建异常对象成本高(需要收集栈轨迹)
- 避免在频繁执行的代码路径中使用异常
- 预检查优于捕获异常
- 自定义异常可重写fillInStackTrace()提升性能
7. Java新特性解析
7.1 Java8核心特性
Lambda表达式典型用法:
java复制// 传统写法
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
// Lambda写法
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());
// 方法引用
list.forEach(System.out::println);
Stream API使用示例:
java复制List<String> result = list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
7.2 Java17新特性
密封类(Sealed Class)示例:
java复制public sealed class Shape
permits Circle, Square, Rectangle {
// 基类定义
}
public final class Circle extends Shape {
private final double radius;
// 实现细节
}
public non-sealed class Square extends Shape {
private final double side;
// 实现细节
}
模式匹配改进:
java复制// instanceof模式匹配
if (obj instanceof String s) {
System.out.println(s.length());
}
// switch模式匹配
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square s -> s.side() * s.side();
case Rectangle r -> r.height() * r.width();
};
8. 综合问题解析
8.1 常见OOM场景分析
内存溢出类型及解决方案:
-
Java堆溢出
- 现象:OutOfMemoryError: Java heap space
- 原因:内存泄漏或堆设置过小
- 解决:分析堆转储(-XX:+HeapDumpOnOutOfMemoryError)
-
元空间溢出
- 现象:OutOfMemoryError: Metaspace
- 原因:动态生成类过多
- 解决:调整-XX:MetaspaceSize
-
栈溢出
- 现象:StackOverflowError
- 原因:递归调用过深
- 解决:检查递归终止条件或调整-Xss
-
直接内存溢出
- 现象:OutOfMemoryError: Direct buffer memory
- 原因:NIO的DirectByteBuffer分配过多
- 解决:调整-XX:MaxDirectMemorySize
8.2 线上问题排查思路
通用排查流程:
- 复现问题:尽可能在测试环境复现
- 收集信息:
- 日志(应用日志、GC日志)
- 线程转储(jstack)
- 堆转储(jmap)
- 分析工具:
- jstat:监控JVM统计信息
- VisualVM:图形化分析
- Arthas:在线诊断工具
- 常见命令:
bash复制# 查看Java进程 jps -l # 查看堆内存使用 jmap -heap <pid> # 生成线程快照 jstack <pid> > thread.dump # 监控GC情况 jstat -gcutil <pid> 1000 10
9. 面试技巧与准备建议
9.1 技术问题回答策略
STAR法则在技术面试中的应用:
- Situation:问题背景
- Task:需要解决的任务
- Action:采取的技术方案
- Result:达到的效果
示例回答:
"在我们电商系统中(Situation),遇到秒杀时数据库压力过大的问题(Task)。我通过引入Redis缓存库存信息,采用Lua脚本保证原子性,并实现本地缓存二级校验(Action)。最终将下单响应时间从2s降到200ms,数据库QPS下降80%(Result)。"
9.2 项目经验阐述要点
项目描述的"三个层次"技巧:
- 宏观层面:项目背景、业务价值
- 技术架构:整体设计、技术选型
- 难点亮点:具体问题的创新解决
避免的常见错误:
- 只讲功能不讲技术
- 夸大个人贡献
- 无法回答细节问题
- 不清楚项目中的不足
10. 持续学习路线建议
10.1 技术深度拓展方向
Java高级进阶路线:
- JVM原理:《深入理解Java虚拟机》
- 并发编程:《Java并发编程实战》
- 性能优化:《Java性能权威指南》
- 源码分析:Spring、Tomcat等主流框架
- 分布式系统:《数据密集型应用系统设计》
10.2 技术广度扩展建议
全栈开发者知识地图:
- 前端基础:HTML/CSS/JavaScript
- 数据库进阶:索引优化、分库分表
- 中间件:Redis/RabbitMQ/Elasticsearch
- 云原生:Docker/Kubernetes/Service Mesh
- 架构设计:DDD、微服务、可观测性
学习资源推荐:
- 极客时间专栏
- InfoQ技术文章
- GitHub Trending项目
- 官方文档(优先阅读英文原版)
