Java 深入解析
text ┌──────────────────────────────────┐ │ 运行时数据区 │ │ ┌─────────┐ ┌─────────┐ │ │ │ 堆 Heap │ │ 方法区 │ │ │ │ (共享) │ │(元空间) │ │ │ └─────────┘ └─────────┘ │ │ ┌─────────┐ ┌─────────┐ │ │ │虚拟机栈 │ │本地方法栈│ │ │ │线程私有 │ │线程私有 │ │ │ └─────────┘ └─────────┘ │ │ 程序计数器 (私有) │ └──────────────────────────────────┘
### 2.2 垃圾回收机制
**对象存活判断**:使用 **可达性分析** 算法,从 GC Roots(栈帧引用、静态属性、常量等)开始搜索,不可达对象可回收。
**垃圾回收算法**:
- **标记-清除**:产生内存碎片
- **标记-复制**:新生代常用,Eden:Survivor = 8:1:1,浪费少量空间换效率
- **标记-整理**:老年代常用,避免碎片
**经典垃圾收集器**:
| 收集器 | 区域 | 特点 |
|-------|------|------|
| Serial | 新生代 | 单线程,暂停所有用户线程 |
| ParNew | 新生代 | Serial 的多线程版本 |
| Parallel Scavenge | 新生代 | 吞吐量优先,自动调节 |
| CMS | 老年代 | 并发标记清除,低延迟,但有浮动垃圾和碎片 |
| G1 | 全部 | 区域化分代,可预测停顿时间,JDK9 默认 |
### 2.3 内存溢出与排查
- **堆溢出**:`java.lang.OutOfMemoryError: Java heap space` → 增大堆 `-Xmx`,用 MAT 分析 dump。
- **栈溢出**:`StackOverflowError` → 检查递归深度或无限循环调用。
- **元空间溢出**:`OOM: Metaspace` → 用 `-XX:MaxMetaspaceSize` 限制,检查动态类生成(如 CGLIB)。
---
## 第三章:并发编程——线程安全的底层逻辑
### 3.1 Java 内存模型 (JMM)
JMM 定义了主内存与线程工作内存的抽象关系:
- 所有变量存储在主内存
- 线程必须将变量从主内存拷贝到工作内存操作,再写回
- 这就导致了 **可见性**、**原子性** 和 **有序性** 问题
**Happens-Before 规则**:如果操作 A happens-before 操作 B,则 A 的结果对 B 可见。常见规则包括程序次序、锁定规则、volatile 规则、传递性等。
### 3.2 volatile 的原理
`volatile` 保证可见性和禁止指令重排序:
- 写 `volatile` 变量会立刻刷新到主内存,并使其他线程的副本失效。
- 底层通过 **内存屏障 (Memory Barrier)** 实现:Store-Load、Store-Store 等屏障防止重排序。
- 经典应用:双检锁单例模式的标志位。
```java
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.3 synchronized 深入
三种形态:
- 普通方法:锁当前实例对象
- 静态方法:锁类的 Class 对象
- 同步块:可以指定锁对象
锁升级过程(JDK 1.6 之后): 无锁 → 偏向锁(记录线程 ID)→ 轻量级锁(CAS 自旋)→ 重量级锁(操作系统互斥量) 偏向锁减少同一线程反复加锁开销,轻量级锁避免线程阻塞,重量级锁适用长时间竞争。
Monitor 机制:每个对象有 ObjectMonitor,包含 _EntryList(阻塞队列)和 _WaitSet(等待队列)。wait/notify 依赖 monitor。
3.4 CAS 与原子类
CAS (Compare-And-Swap) 是乐观锁的基础,CPU 原子指令。AtomicInteger 等使用 CAS 实现无锁线程安全。
ABA 问题:可用 AtomicStampedReference 增加版本号解决。
第四章:集合框架——源码解析与性能对比
4.1 ArrayList 与 LinkedList
- ArrayList:基于动态数组,随机访问 O(1),尾部插入块,中间插入删除 O(n)。扩容:新容量 = 旧容量 * 1.5,通过
Arrays.copyOf复制。 - LinkedList:双向链表,插入删除 O(1)(给定节点),随机访问 O(n)。适合频繁头尾操作。
如何选择?读多写少用 ArrayList,频繁插入删除用 LinkedList。
4.2 HashMap 深度原理
数据结构:数组 + 链表/红黑树(JDK8+)
- 初始容量 16,负载因子 0.75,扩容时容量翻倍。
- 哈希计算:
(h = key.hashCode()) ^ (h >>> 16)高低位混合,再(n-1) & hash定位桶下标。 - 链表长度 >=8 且数组长度 >=64 时转为红黑树(提升查找效率 O(n) → O(log n))。
- 线程不安全,多线程可能造成死循环(JDK7 扩容时头插法倒序,JDK8 改为尾插法但仍可能数据覆盖)。
ConcurrentHashMap:分段锁思想,JDK8 中用 CAS + synchronized 锁桶头节点,高效支持并发。
4.3 LinkedHashMap 实现 LRU
继承 HashMap,内部维护双向链表保持插入顺序或访问顺序。设置 accessOrder=true,重写 removeEldestEntry 可实现简单 LRU 缓存。
第五章:泛型与类型擦除
5.1 类型擦除
Java 泛型通过编译时类型检查 + 类型擦除实现:
- 编译后泛型信息被擦除,替换为其限定类型(无限定则 Object)。
- 导致不能直接
new T(),不能用instanceof泛型类型(可用通配符绕过)。 - 泛型方法可通过反射获取参数化类型的实际类型(如果子类继承指定了具体类型)。
// 编译后 class 文件中 List<String> 和 List<Integer> 都是 List
List<String> list = new ArrayList<>();
list.getClass() == new ArrayList<Integer>().getClass(); // true
5.2 通配符与 PECS 原则
<? extends T>:上界通配符,生产元素类型,只能读不能写(生产者 - Extends)<? super T>:下界通配符,消费者,可写入 T 或子类,读出的类型只能是 Object(消费者 - Super)- PECS:从集合读取用 extends,向集合写入用 super。
public void copy(List<? super T> dest, List<? extends T> src) { ... }
第六章:反射与动态代理
6.1 反射机制
反射可以在运行时获取类的信息、动态创建对象和调用方法。核心类:Class、Method、Field、Constructor。
- 获得 Class 对象:
类名.class、对象.getClass()、Class.forName() - 创建实例:
newInstance()或Constructor.newInstance() - 调用方法:
method.invoke(target, args...) - 开销:比直接调用慢很多,但通过
setAccessible(true)关闭访问检查可优化。
6.2 动态代理原理
JDK 动态代理:
- 基于接口,使用
Proxy.newProxyInstance()生成代理类。 - 本质:运行时生成一个
$Proxy类,实现传入的接口,所有方法调用转发到InvocationHandler.invoke()方法。 - 核心代码通过字节码组装,包含 equals/hashCode/toString 和接口方法的转发。
CGLIB 代理:
- 基于继承,通过 ASM 生成目标类的子类,重写方法加入拦截逻辑。
- 不能代理 final 类或 final 方法。
应用:Spring AOP 根据目标是否实现接口自动选择 JDK 或 CGLIB 代理。
第七章:Lambda 表达式与 Stream API
7.1 Lambda 背后的函数式接口
Lambda 表达式需要函数式接口(只有一个抽象方法),形如 (参数) -> { 方法体 }。
- 常见接口:
Predicate<T>(test)、Consumer<T>(accept)、Function<T,R>(apply)、Supplier<T>(get)。 - Lambda 不会生成匿名内部类,而是通过
invokedynamic指令在运行时生成调用点,性能优于匿名类。
7.2 Stream 的惰性求值与终端操作
流操作分为中间操作(filter、map、sorted 等,惰性)和终端操作(collect、forEach、reduce 等,触发计算)。
- 并行流:
parallelStream()使用 ForkJoinPool,适合数据量大且独立计算,注意线程安全。 - 常用收集器:
Collectors.toList/toSet/toMap,groupingBy,partitioningBy。
7.3 Optional 避免 NPE
Optional 是值的容器,强制开发者显式处理 null 情况。
Optional.ofNullable(value)
.map(String::trim)
.filter(s -> s.length() > 0)
.orElse("default");