Java面试核心知识点:值传递、集合框架与并发编程
1. Java基础面试题精选
1.1 Java中的值传递与引用传递
在Java面试中,值传递和引用传递是必考的基础知识点。很多初学者容易混淆这两者的区别,我们先来看一个典型例子:
public class PassByValueExample { public static void main(String[] args) { int num = 10; modifyValue(num); System.out.println(num); // 输出仍然是10 User user = new User("John"); modifyReference(user); System.out.println(user.getName()); // 输出变为"Mike" } public static void modifyValue(int param) { param = 20; } public static void modifyReference(User param) { param.setName("Mike"); } } class User { private String name; // 构造方法和getter/setter省略 }这里的关键理解点是:
- Java中只有值传递,没有引用传递
- 对于基本类型,传递的是值的副本
- 对于对象类型,传递的是引用的副本(即指针的拷贝)
注意:面试时经常会被问到"为什么Java中只有值传递",需要能清晰解释对象引用传递的本质是传递引用的副本。
1.2 String的不可变性
String的不可变性是Java设计中的一个重要特性,理解它对写出高效代码很有帮助:
String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello"); String s4 = s3.intern(); System.out.println(s1 == s2); // true,指向字符串常量池同一对象 System.out.println(s1 == s3); // false,s3是堆中新对象 System.out.println(s1 == s4); // true,intern返回常量池引用实际开发中的经验:
- 频繁字符串拼接应该用StringBuilder
- 大量相同字符串可以使用intern()方法节省内存
- 敏感信息(如密码)应该用char[]而不是String存储
2. Java集合框架深度解析
2.1 HashMap的工作原理
HashMap是面试中最常被问到的集合类,其核心实现要点包括:
- 数据结构:数组+链表/红黑树(JDK8+)
- 哈希函数:hash = (h = key.hashCode()) ^ (h >>> 16)
- 扩容机制:默认负载因子0.75,扩容时容量翻倍
// 典型面试问题:HashMap多线程下死循环问题 // JDK7版本中,多线程扩容可能导致环形链表 // 解决方案:使用ConcurrentHashMap或Collections.synchronizedMap2.2 ArrayList与LinkedList对比
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 头部插入 | O(n) | O(1) |
| 内存占用 | 更紧凑 | 每个元素额外指针 |
| 适用场景 | 读多写少 | 写多读少 |
实际开发建议:
- 预估数据量时,ArrayList初始化指定容量避免扩容
- 频繁在中间位置插入删除考虑LinkedList
- 随机访问超过10万次时ArrayList优势明显
3. 并发编程核心问题
3.1 synchronized实现原理
synchronized关键字的底层实现经历了多次优化:
- JDK1.6前:直接使用操作系统的互斥锁,性能差
- 锁升级过程:
- 无锁 -> 偏向锁(单线程)
- 偏向锁 -> 轻量级锁(少量竞争)
- 轻量级锁 -> 重量级锁(激烈竞争)
// 双重检查锁定单例模式 public class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }注意:volatile防止指令重排序,保证可见性
3.2 ThreadLocal内存泄漏问题
ThreadLocal使用不当会导致内存泄漏:
public class ThreadLocalLeakDemo { private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(() -> { threadLocal.set(new byte[10 * 1024 * 1024]); // 10MB // 忘记调用remove() }).start(); } }解决方案:
- 使用后必须调用remove()清理
- 声明为static final减少实例数量
- 使用WeakReference改进的ThreadLocal实现
4. JVM性能调优实战
4.1 内存区域与GC算法
JVM内存结构关键点:
- 程序计数器:线程私有,唯一不会OOM的区域
- 虚拟机栈:栈帧(局部变量表、操作数栈等)
- 堆:对象实例,GC主要区域
- 方法区:类信息、常量等(JDK8后元空间)
GC算法对比:
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Serial | 简单高效 | 单线程STW长 | 客户端小内存 |
| Parallel | 多线程吞吐量高 | 仍会STW | 后台计算型应用 |
| CMS | 并发收集停顿短 | 内存碎片多 | 互联网B/S系统 |
| G1 | 可预测停顿时间 | 内存占用较大 | 大内存服务 |
| ZGC | 超低延迟(10ms内) | JDK11+ | 对延迟敏感系统 |
4.2 OOM问题排查步骤
典型内存溢出排查流程:
- 使用jps查看Java进程ID
- jstat -gcutil [pid] 查看GC情况
- jmap -histo:live [pid] 查看对象分布
- jmap -dump:format=b,file=heap.hprof [pid] 导出堆转储
- 使用MAT或VisualVM分析dump文件
常见OOM场景:
- 堆溢出:java.lang.OutOfMemoryError: Java heap space
- 元空间溢出:java.lang.OutOfMemoryError: Metaspace
- 栈溢出:java.lang.StackOverflowError
- 直接内存溢出:java.lang.OutOfMemoryError: Direct buffer memory
5. 新特性与设计模式
5.1 Java8 Stream API实战
Stream操作的三阶段:
- 创建流:stream()、parallelStream()、Arrays.stream()
- 中间操作:filter()、map()、distinct()、sorted()
- 终止操作:forEach()、collect()、reduce()、count()
// 典型用例:统计单词频率 List<String> words = Arrays.asList("hello", "world", "hello"); Map<String, Long> wordCount = words.stream() .collect(Collectors.groupingBy( Function.identity(), Collectors.counting() ));性能注意事项:
- 小数据量时stream可能比循环慢
- parallelStream要谨慎使用(数据量>1万考虑)
- 避免在stream内修改外部状态
5.2 单例模式的六种实现
从简单到复杂的单例实现演进:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(同步方法)
- 双重检查锁定
- 静态内部类
- 枚举(最推荐)
// 枚举实现单例(防反射攻击) public enum Singleton { INSTANCE; public void doSomething() { // 业务方法 } }枚举单例的优势:
- 线程安全
- 防止反射创建新实例
- 防止反序列化创建新实例
- 代码简洁
6. Spring框架核心原理
6.1 IOC容器工作流程
Spring IOC核心处理流程:
- 资源定位:找到配置文件(xml/注解)
- 加载解析:BeanDefinition的读取与注册
- 依赖注入:属性填充(autowire)
- 初始化:aware接口回调、init-method
- 使用:通过getBean获取实例
- 销毁:disposableBean接口、destroy-method
// 自定义BeanPostProcessor示例 public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("初始化前处理: " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("初始化后处理: " + beanName); return bean; } }6.2 Spring事务传播机制
七种传播行为对比:
| 传播行为类型 | 说明 |
|---|---|
| REQUIRED(默认) | 当前有事务则加入,没有则新建 |
| SUPPORTS | 当前有事务则加入,没有则以非事务运行 |
| MANDATORY | 当前必须有事务,否则抛异常 |
| REQUIRES_NEW | 新建事务,挂起当前事务(完全独立) |
| NOT_SUPPORTED | 以非事务执行,挂起当前事务 |
| NEVER | 以非事务执行,当前有事务则抛异常 |
| NESTED | 嵌套事务,外层异常时回滚内层(基于保存点) |
实际开发经验:
- 99%场景用REQUIRED或REQUIRES_NEW
- 嵌套事务(NESTED)使用较少且数据库要支持
- 注意@Transactional注解失效场景(同类调用、非public方法等)
7. 数据库与缓存优化
7.1 MySQL索引优化原则
索引设计黄金法则:
- 最左前缀原则:联合索引(a,b,c)只能按a、ab、abc顺序使用
- 覆盖索引:查询列都在索引中,避免回表
- 索引下推:MySQL5.6+特性,减少回表次数
- 避免索引失效:
- 使用!=、<>、not in
- 函数操作如LEFT(name,3)='abc'
- 类型转换如字符串列用数字查询
- or条件未全部索引
-- 索引失效案例 SELECT * FROM users WHERE age+10>30; -- 表达式运算 SELECT * FROM users WHERE DATE(create_time)='2023-01-01'; -- 函数操作 SELECT * FROM users WHERE name LIKE '%abc'; -- 左模糊7.2 Redis持久化策略
两种持久化方式对比:
| 特性 | RDB(快照) | AOF(日志) |
|---|---|---|
| 数据完整性 | 可能丢失最后一次更改 | 通常更完整(可配置) |
| 恢复速度 | 更快 | 较慢 |
| 磁盘占用 | 较小(二进制压缩) | 较大(文本命令) |
| 性能影响 | 子进程方式影响较小 | 同步写入影响较大 |
| 适用场景 | 数据备份、灾难恢复 | 需要更高数据安全性 |
生产环境建议:
- 主从架构中主库关闭持久化
- 从库开启RDB+AOF混合模式
- AOF配置为everysec平衡性能与安全
- 定期检查备份文件有效性
8. 分布式系统设计
8.1 CAP理论实践应用
CAP三选二的实际含义:
- 一致性(C):所有节点看到相同数据
- 可用性(A):每个请求都能得到响应
- 分区容错性(P):网络分区时系统仍能运行
分布式系统设计选择:
| 系统类型 | 选择 | 典型代表 |
|---|---|---|
| CP系统 | 放弃A | ZooKeeper、Etcd |
| AP系统 | 放弃C | Cassandra、Eureka |
| CA系统 | 放弃P | 单机数据库 |
实际经验:
- 金融系统通常选择CP(如支付系统)
- 互联网应用多选择AP(如社交网络)
- 不存在完美的CA系统(网络分区不可避免)
8.2 分布式锁实现方案
常见分布式锁实现对比:
| 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 数据库 | 实现简单 | 性能差、死锁风险 | 低并发简单场景 |
| Redis | 性能高 | 锁续期复杂 | 高并发短期锁定 |
| ZooKeeper | 可靠性高 | 性能中等 | 需要高可靠性的场景 |
| etcd | 性能较好 | 学习成本较高 | 云原生环境 |
Redis分布式锁最佳实践:
// Redisson实现示例 RLock lock = redisson.getLock("myLock"); try { // 尝试加锁,最多等待100秒,上锁后30秒自动解锁 boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS); if (res) { // 业务代码 } } finally { lock.unlock(); }关键点:
- 必须设置过期时间
- 加锁与设置过期时间必须原子操作
- 考虑锁续期问题(看门狗机制)
- 释放锁要判断持有者
9. 系统设计方法论
9.1 秒杀系统设计要点
高性能秒杀系统核心架构:
分层削峰:
- 前端:验证码、答题、按钮置灰
- 网关:限流(令牌桶/漏桶)
- 服务:队列缓冲、异步处理
- 存储:库存预热、分段扣减
热点数据处理:
- Redis集群+本地缓存
- 库存分段(如1000库存分成10个段)
- 避免大事务(先扣库存再创建订单)
防作弊措施:
- 用户行为分析
- 设备指纹识别
- 购买频率限制
// 分布式库存扣减示例(Lua脚本保证原子性) String script = "if redis.call('exists',KEYS[1])==1 then\n" + " local stock = tonumber(redis.call('get',KEYS[1]))\n" + " if stock>0 then\n" + " redis.call('decr',KEYS[1])\n" + " return stock-1\n" + " end\n" + " return -1\n" + "end\n" + "return -2";9.2 微服务拆分原则
领域驱动设计(DDD)拆分方法:
战略设计:
- 识别限界上下文(如订单、支付、物流)
- 定义上下文映射关系(合作关系、客户-供应商等)
战术设计:
- 实体(有唯一标识)
- 值对象(通过属性定义)
- 聚合根(外部访问入口)
- 领域服务(跨实体的业务逻辑)
拆分经验法则:
- 团队规模:2Pizza原则(6-10人团队)
- 业务变化频率:高频变化部分独立
- 性能需求:高并发模块单独部署
- 技术异构:不同技术栈需求分离
10. 性能优化全链路
10.1 JVM参数调优指南
生产环境JVM参数配置示例:
# JDK8推荐配置 java -Xms4g -Xmx4g \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m \ -Xmn2g \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:ParallelGCThreads=4 \ -XX:ConcGCThreads=2 \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/path/to/dump.hprof \ -jar application.jar关键参数说明:
- Xms/Xmx:堆内存初始/最大值(建议设为相同)
- MetaspaceSize:元空间初始大小
- Xmn:新生代大小(通常为堆的1/2到1/3)
- UseG1GC:使用G1垃圾收集器
- MaxGCPauseMillis:目标最大停顿时间
10.2 全链路压测要点
生产环境压测实施步骤:
环境准备:
- 影子表/影子库
- 流量镜像
- 数据脱敏
压测场景设计:
- 基准测试(单接口)
- 负载测试(逐步增加压力)
- 压力测试(极限情况)
- 稳定性测试(长时间运行)
监控指标:
- 应用层:QPS、RT、错误率
- 系统层:CPU、内存、IO、网络
- 中间件:连接池、线程池、队列
- 数据库:TPS、锁等待、慢查询
优化闭环:
- 瓶颈定位(火焰图、线程dump)
- 优化实施(代码/配置/架构)
- 验证效果(对比测试)
