Week 1 · Chapter 3 · Java/JVM

复习难度:⭐⭐ | 预计时长:2.5小时 | 重点程度:中


3.1 JVM 内存结构

原理

JVM 运行时数据区分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)。

┌─────────────────────────────────────────┐
│                  Heap                   │  ← 线程共享,最大区域
│  ┌──────────────┐  ┌─────────────────┐  │
│  │    Young     │  │      Old        │  │
│  │ Eden S0 S1  │  │   (Tenured)     │  │
│  └──────────────┘  └─────────────────┘  │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│              Method Area               │  ← 线程共享(1.8 后用 Metaspace)
│  类信息、常量、静态变量、JIT编译代码    │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│   PC Reg  │  VM Stack  │ Native Stack   │  ← 线程私有
└─────────────────────────────────────────┘

各区域职责:

区域 存储内容 异常
程序计数器 当前线程执行的字节码行号
VM Stack 方法栈帧(局部变量表、操作数栈) StackOverflowError / OOM
Heap 对象实例、数组 OOM
Method Area 类元数据、运行时常量池(1.8 后 Metaspace) OOM(Metaspace 可动态扩展)
Native Stack JNI 本地方法栈 StackOverflowError / OOM

常见面试题

Q1:对象在堆中的分配过程?

答:① 新对象先尝试在 Eden 区分配;② Eden 满时触发 Minor GC,存活对象进入 Survivor 区(S0/S1),年龄+1;③ 年龄超过阈值(默认15,-XX:MaxTenuringThreshold)进入老年代;④ 大对象直接进入老年代(-XX:PretenureSizeThreshold)。

Q2:方法区和 Metaspace 的区别?

答:Java 8 之前,方法区是堆的一部分(永久代),大小固定易 OOM。Java 8 后,类元数据移到native 内存的 Metaspace,不再受堆大小限制,可以通过 -XX:MetaspaceSize-XX:MaxMetaspaceSize 配置。


3.2 GC 算法

原理

常见 GC 算法对比:

算法 原理 优点 缺点
标记-清除 先标记存活对象,再清除未标记 简单 产生内存碎片
复制 将存活对象复制到另一半 无碎片,高效 浪费一半空间
标记-整理 标记后移动存活对象紧邻 无碎片 移动成本
分代收集 Young(复制)/Old(标记-整理) 优化局部性 参数调优复杂

HotSpot 分代策略:

Young Generation:
  Eden (8) + Survivor x2 (1:1:1)
  → 触发 Minor GC,存活对象进入 S 区,年龄+1
  → 多次回收后仍存活 → Old Generation

Old Generation:
  → 触发 Major GC / Full GC(STW 时间长)

G1(Garbage First)——JDK 11+ 默认: - 把堆划分为多个大小相等的 Region(1MB~32MB) - 优先回收垃圾最多的 Region(First) - 可设置 -XX:MaxGCPauseMillis 控制停顿时间

常见面试题

Q1:Minor GC 和 Full GC 的触发条件?

答:Minor GC:Eden 区满(JVM 规范,实际 survivor 满也会)。Full GC:① 老年代空间不足;② Metaspace 满;③ System.gc()(可能触发);④ malloc 无法从 OS 分配内存。

Q2:G1 的 ZGC 有什么区别?

答:ZGC 是 JDK 11+ 的低延迟 GC,停顿时间 < 1ms,使用着色指针(colored pointer)做并发标记/整理,支持 TB 级堆。代价是吞吐略低于 G1。


3.3 线程池

原理

Java 线程池通过 Executor 框架实现:

// 常用线程池创建方式
ExecutorService pool = Executors.newFixedThreadPool(10);  // 固定大小
ExecutorService pool = Executors.newCachedThreadPool();     // 按需创建
ExecutorService pool = Executors.newSingleThreadExecutor(); // 单线程

// 推荐:手动用 ThreadPoolExecutor
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    5,                      // corePoolSize
    20,                     // maximumPoolSize
    60L, TimeUnit.SECONDS,  // keepAliveTime
    new LinkedBlockingQueue<>(100),  // 队列容量
    new ThreadFactory()...,  // 自定义线程名
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

线程池拒绝策略:

策略 行为
AbortPolicy(默认) 抛 RejectedExecutionException
CallerRunsPolicy 由调用线程执行任务
DiscardPolicy 静默丢弃
DiscardOldestPolicy 丢弃队列最旧的任务

工作流程:

新任务 → corePoolSize 未满 → 创建新线程执行
      → corePoolSize 满 → 加入阻塞队列
      → 队列满 & maximumPoolSize 未满 → 创建新线程
      → 队列满 & maximumPoolSize 满 → 执行拒绝策略

常见面试题

Q1:线程池的核心参数如何设置?

答:取决于任务类型: - CPU 密集型CPU核数 + 1,减少线程竞争 - IO 密集型CPU核数 / (1 - 阻塞系数),阻塞系数 0.8~0.9 时可设 2~4 倍 CPU 核数 - 队列容量:按峰值 QPS × 平均耗时估算

Q2:为什么阿里巴巴规范禁止使用 Executors 创建线程池?

答:newFixedThreadPoolnewSingleThreadPool 使用 LinkedBlockingQueue(无界队列),任务过多时会导致 OOM;newCachedThreadPool 线程数无上限,高并发下可能创建过多线程拖垮系统。

Q3:Go 的 goroutine 池和 Java 线程池的本质区别?

答:Java 线程池的线程是 OS 线程(1:1),线程创建/切换成本高;Go 的 GMP 模型是 M:N,百万级并发下 goroutine 调度开销远低于 Java 线程。


3.4 JVM 调优实战

常用参数

# 堆大小配置
-Xms4g -Xmx4g          # 初始/最大堆(生产应设相同避免动态调整)
-Xmn2g                  # Young 区大小

# G1 配置
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=4m

# Metaspace
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

# GC 日志
-Xlog:gc*:file=gc.log:time:filecount=5,filesize=10m

常见面试题

Q1:JVM OOM 如何排查?

答:① 看堆 dump(-XX:+HeapDumpOnOutOfMemoryError);② 用 MAT / JProfiler 分析对象占用;③ 确认是否是 Metaspace 泄漏(类加载器问题);④ 检查 JNI 导致的 native 内存泄漏。

Q2:JVM 频繁 Full GC 但堆使用率不高?

答:可能是 Metaspace 满了(类不断加载/元数据泄漏);也可能是使用 DirectByteBuffer 导致的 native 内存泄漏。添加 -XX:NativeMemoryTracking=detail 排查。


📝 本章高频问题速记

问题 核心答案
JVM 内存区域(线程共享) Heap + Method Area(Metaspace)
JVM 内存区域(线程私有) PC寄存器 + VM Stack + Native Stack
Minor GC 触发条件 Eden 区满
Full GC 触发条件 老年代满 / Metaspace满 / System.gc()
G1 vs ZGC G1 可控停顿,ZGC <1ms 低延迟
线程池拒绝策略 Abort/CallerRuns/Discard/DiscardOldest
CPU 密集型线程池大小 CPU核数 + 1
阿里规范禁止 Executors 无界队列 OOM / 无限创建线程