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 创建线程池?
答:
newFixedThreadPool和newSingleThreadPool使用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 / 无限创建线程 |