Java 深入解析

FreeGuideOnline 最新 2026-07-15

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 反射机制

反射可以在运行时获取类的信息、动态创建对象和调用方法。核心类:ClassMethodFieldConstructor

  • 获得 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/toMapgroupingBypartitioningBy

7.3 Optional 避免 NPE

Optional 是值的容器,强制开发者显式处理 null 情况。

Optional.ofNullable(value)
        .map(String::trim)
        .filter(s -> s.length() > 0)
        .orElse("default");