第 1 天学习任务
目标:JVM 核心 + 线程池基础,1.5 小时
一、学习内容
-
JVM
-
字节码指令:启动项目,java文件编译成字class文件,class文件就是由一个个字节码指令组成的,这些字节码,都是存在“元空间”中
-
线程隔离区:
- 程序计数器:当前线程执行的字节码行号指示器,记录下一条字节码指令地址;线程私有,唯一不会发生 OOM 的 JVM 区域
- 虚拟机栈:描述 Java 方法执行的内存模型,每个执行方法对应一个栈帧,栈帧操作仅入栈 / 出栈(先进后出);线程私有,会发生 StackOverflowError 和 OOM
- 栈帧包括
- 局部变量表:存方法参数 + 局部变量,基本类型存值,引用类型存地址
- 操作数栈:按字节码指令入栈 / 出栈,临时存储操作数和中间结果
- 方法出口:存调用该方法的下一条指令地址
- 异常表:- 存异常处理信息,包含起始 / 结束 / 跳转指令地址
- 动态链接:保存符号引用到运行时常量池直接引用的映射,调用方法 / 属性时通过编号快速获取地址
- 本地方法栈:为 JVM 执行 Native 方法提供内存支持;线程私有,会发生 StackOverflowError 和 OOM
-
线程共享区(所有线程共用,GC 主要回收区域):
- 堆:JVM 最大的内存区域,JDK7 后字符串常量池移至此区域:
- 存储内容:实例对象、数组
- 核心特性:线程共享,会发生 OOM,是 GC(Minor GC/Full GC)的核心回收区域
- 元空间:JDK8 及以后对方法区的实现,存储在本地内存(非 JVM 堆内存)中(JDK7 及以前方法区是永久代,属于堆内存)
- 储存内容:- 类信息(class 文件、方法 / 属性 / 修饰符 / 字节码指令)、运行时常量池、编译代码(动态代理类)、静态变量(static 修饰)
- 运行时常量池:存编译期生成的基本类型变量、类 / 方法的全限定名
- 堆:JVM 最大的内存区域,JDK7 后字符串常量池移至此区域:
-
垃圾回收
- 垃圾回收算法
| 算法 | 执行步骤 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 标记 - 清除 | 标记回收对象 → 统一清除 | 实现简单 | 产生内存碎片 | 极少单独使用 |
| 标记 - 复制 | 标记存活对象 → 复制到空内存 → 清空原内存 | 无碎片、速度快 | 浪费一半内存空间 | 年轻代(存活对象少) |
| 标记 - 整理 | 标记存活对象 → 移动至一端 → 清除边界外内存 | 无碎片、不浪费空间 | 效率慢 | 老年代(存活对象多) |
- Java堆内存分代模型(堆 = 年轻代 + 老年代)
-
方法区/元空间:类信息、常量、静态变量
-
年轻代 Young Gen
- 特点:对象朝生夕死,存活率极低,GC 频繁(Minor GC)、速度快
- 内部结构:Eden 区:(新对象出生的地方) + 2 个对等的 Survivor 区(From/To)
- **Survivor From/To 工作机制 **: Minor GC 时,将 Eden+From 区的存活对象复制到 To 区,清空 Eden+From,互换 From/To 身份;存活对象每经历 1 次 Minor GC 年龄 + 1
- 对象晋升老年代条件:
- ① 年龄达到阈值(默认 15);
- ② 大对象直接进入;
- ③ Survivor 区相同年龄对象大小超过该区一半
-
老年代 Old Gen
- 特点:对象存活时间长,GC 频率低(Full GC)、耗时久
- 回收算法:标记 - 整理
- 触发老年代 GC(Full GC)常见原因:
- 老年代空间不足
- 元空间不足
- 调用 System.gc ()
- 年轻代大量对象晋升,老年代放不下
-
分代回收的核心意义:不同对象生命周期不同,对应使用最优回收算法,最大化 GC 效率(年轻代用复制算法快,老年代用整理算法不浪费空间)
-
常见垃圾收集器:
- 年轻代:Serial、Parallel Scavenge、ParNew
- 老年代:Serial Old、Parallel Old、CMS
- 整堆回收:(JDK8 + 主流,JDK9 + 默认):G1
- G1 核心特点:不分物理上的年轻 / 老年代,将堆划分为多个 Region;可预测停顿时间;兼顾吞吐量和延迟。
-
- 常用排查命令
-
jstack(排查线程问题)
- 作用:查看 Java 进程的线程快照,找死锁、线程卡顿、CPU 高
- 常用命令:
- jstack
<pid>: 查看指定进程的线程信息(pid是进程号) - jstack
<pid>| grep -i deadlock : 查找死锁(直接输出死锁信息)
- jstack
-
jmap(排查内存问题)
- 常用命令:
- jmap -heap
<pid>: 查看堆内存概况 - jmap -dump:format=b,file=heap.hprof
<pid>: 导出堆快照(生成hprof文件,用MAT分析)
- jmap -heap
- 常用命令:
-
Arthas(阿里开源,万能排查工具)
- 作用:不用重启服务,在线排查 JVM、线程、方法调用
- 常用命令:
- java -jar arthas-boot.jar : 启动arthas
- thread : 查看线程CPU占用
- dashboard : 查看JVM信息
- trace 类名 方法名 监控方法执行耗时
-
-
线程池
- 为什么要用线程池:
- 避免频繁创建 / 销毁线程,降低系统开销;
- 控制并发线程数,防止线程过多导致资源耗尽;
- 统一管理线程,支持监控、调优、任务拒绝。
- 线程池核心参数:
- corePoolSize:核心线程数(默认一直存活,可通过 allowCoreThreadTimeOut (true) 设置空闲回收)
- maximumPoolSize:线程池最大线程数(核心 + 非核心)
- keepAliveTime:非核心线程空闲存活时间
- unit:keepAliveTime 的时间单位(TimeUnit)
- workQueue:阻塞队列,用于存放等待执行的任务
- threadFactory:线程工厂,用于创建线程(自定义线程名称 / 优先级)
- handler:任务拒绝策略(队列满 + 线程数达最大值时触发)
- 任务执行流程:
- 任务提交 → 核心线程未满→创建核心线程执行 → 核心线程满→放入阻塞队列 → 队列满→创建非核心线程执行 → 线程数达最大值→执行拒绝策略
- 4 种默认拒绝策略:
- AbortPolicy:直接抛出 RejectedExecutionException(默认策略)
- DiscardPolicy:直接丢弃任务,无任何提示
- DiscardOldestPolicy:丢弃队列中最老的任务,将新任务入队
- CallerRunsPolicy:由提交任务的线程自己执行该任务