jvm知识汇总

JVM 内存模型

一、Java 内存模型(JMM,Java Memory Model)

关注"多线程下变量如何可见、有序",是语言规范层面的概念,和硬件内存模型有关,但不是 JVM 内部数据结构。

1. 核心问题

  • 多线程共享变量时,一个线程的写操作,何时对其他线程可见?

  • 编译器和处理器为了性能会做重排序,如何保证程序语义不变?

2. 抽象结构

  • 主内存(Main Memory):所有线程共享,存放实例字段、静态字段等。

  • 工作内存(Working Memory):每个线程私有,保存该线程使用的变量的副本。

  • 线程不能直接读写主内存中的变量,必须通过工作内存:

  • 读:主内存 → 工作内存

  • 写:工作内存 → 主内存

这里的"主内存 / 工作内存"是逻辑概念,不等同于堆 / 栈。

3. 关键机制

  • happens-before 原则:定义操作之间的可见性顺序,常见规则:

  • 程序顺序规则

  • 监视器锁规则(解锁先于后续加锁)

  • volatile 变量规则

  • 线程启动 / 终止规则

  • volatile:

  • 可见性:写立即刷新到主内存,读从主内存加载

  • 禁止部分重排序(通过内存屏障)

  • synchronized / Lock:保证原子性 + 可见性 + 有序性

  • final:正确构造的对象,final 字段对其他线程可见

二、JVM 运行时数据区(Runtime Data Areas)

关注"JVM 进程在运行 Java 程序时,内存如何划分",是 JVM 实现层面的结构。

以下以 HotSpot + Java 8+ 为主说明。

1. 线程私有区域

(1)程序计数器(PC Register)

  • 当前线程所执行字节码的行号指示器

  • 线程切换后能恢复到正确执行位置

  • 唯一不会 OOM 的区域

(2)Java 虚拟机栈(JVM Stack)

每个方法调用对应一个栈帧(Stack Frame)

栈帧包含:

  • 局部变量表

  • 操作数栈

  • 动态链接

  • 方法返回地址

出现异常:

  • "StackOverflowError":栈深度过大

  • "OutOfMemoryError":栈扩展失败

(3)本地方法栈(Native Method Stack)

  • 为 Native 方法(JNI)服务

  • HotSpot 中常与虚拟机栈合二为一

2. 线程共享区域

(4)Java 堆(Heap)

  • 最大的一块内存区域

  • 存放对象实例和数组

  • GC 的主要区域

  • 可细分为:

  • 新生代(Eden、Survivor)

  • 老年代

  • 异常:

"OutOfMemoryError: Java heap space"

(5)方法区(Method Area)

存储:

  • 类信息(类名、字段、方法、字节码等)

  • 常量

  • 静态变量

  • JIT 编译后的代码

  • Java 8 之前:永久代(PermGen)

  • Java 8+:元空间(Metaspace),使用本地内存

异常:

"OutOfMemoryError: Metaspace"

(6)运行时常量池(Runtime Constant Pool)

  • 方法区的一部分

  • 存放编译期生成的字面量和符号引用

  • 运行时也可放入新常量(如

"String.intern()")

3. 直接内存(Direct Memory)

  • 不属于 JVM 运行时数据区

  • 通过 "ByteBuffer.allocateDirect()" 分配

  • 受本机内存限制

异常:

"OutOfMemoryError"

三、总结

  • JMM:解决"多线程下变量怎么看得到、不乱序"

  • JVM 内存结构:解决"Java 程序运行时的内存怎么分、放哪"

垃圾回收机制

一、是什么

JVM 垃圾回收(GC)的核心就一句话:自动找到不再使用的对象,回收它们占用的内存。

二、怎么判断对象"死了"?

  1. 引用计数法(不用)
    给每个对象记一个计数器,被引用就 +1,引用失效就 -1,变成 0 就回收。
    ❌ 缺点:两个对象互相引用就永远回收不了(循环引用问题)
  2. 可达性分析(JVM 实际用的)
    从一组 "GC Roots" 出发,顺着引用链往下找:
    能找到的对象 → 存活
    打个比方:GC Roots 是树根,顺着根能摸到的都是活的,摸不到的就是"垃圾"。
    找不到的对象 → 可回收
    GC Roots是判断对象是否存活的起点。从这些根节点开始,能通过引用链访问到的对象就是"活的",反之就是"垃圾"
    有哪些
    虚拟机栈中引用的对象(当前方法中的局部变量)
    本地方法栈中JNI引用的对象
    方法区中静态属性引用的对象(static变量)
    方法区中常量引用的对象(final常量)
    JVM内部的引用(如Class对象、异常对象、类加载器等)
    一句话总结:GC Roots是那些"绝对不会被回收"的对象的集合,是垃圾回收的起点。
    打个比方:GC Roots 是树根,顺着根能摸到的都是活的,摸不到的就是"垃圾"。

三、垃圾怎么清?

  1. 标记-清除(Mark-Sweep)
    先标记垃圾,再统一清除
    ❌ 缺点:产生大量内存碎片
  2. 复制算法(Copying)
    把内存分成两半,只用一半
    存活对象复制到另一半,然后清空这一半
    ✅ 没有碎片
    ❌ 缺点:只能用一半内存
  3. 标记-整理(Mark-Compact)
    标记后,把存活对象往一端挪,然后清理边界外的内存
    ✅ 没有碎片,空间利用率高
    ❌ 缺点:移动对象成本高
  4. 分代收集(实际 JVM 用的)
    核心思想:大部分对象活不长。
    把堆分成几块,不同区域用不同策略

四、对象在内存里怎么放的?

新生代:新创建的对象都放这,大多数对象"朝生夕死"
Eden(伊甸园)+ 两个 Survivor 区
老年代:在新生代活过几轮 GC 还没死的对象,搬到这里
为什么分代?因为大部分对象活不长,可以针对性地频繁清理新生代,减少全堆扫描的开销。

对象的一生:

新对象先放 Eden
Eden 满了 → 触发 Minor GC,存活的放到 S0(先放到S1,然后回收eden和S0,S0和S1互换)
下次 GC,Eden + S0 存活的复制到 S1,清空 Eden + S0
每熬过一次 GC,年龄 +1
年龄到阈值(默认 15)→ 晋升 老年代
老年代也满了 → 触发 Full GC(全局回收,最慢)
五、垃圾回收器(简单了解)

  1. Serial:单线程,简单但会"Stop The World"(暂停所有用户线程)
  2. Parallel / Parallel Old:多线程并行,吞吐量优先
  3. CMS:并发标记清除,停顿短,但已废弃
  4. G1(主流):把堆分成很多小 Region,可预测停顿时间,平衡吞吐和延迟
  5. ZGC / Shenandoah:超低延迟(停顿 < 10ms),适合超大堆

总结

JVM GC 基于可达性分析识别垃圾,按分代采用不同回收算法(新生代复制、老年代整理),并由各类垃圾回收器(如 G1、ZGC)负责具体执行。

JVM调优

线上GC排查

相关推荐
头茬韭菜3 小时前
第 1 篇:「架构鸟瞰与 Memory 初始化」—— 从 pip install 到三个工厂
jvm·架构·pip·mem0
程序员清风3 小时前
从 JVM 内存模型到 GC 调优:Java 服务性能优化实战
java·jvm·性能优化
OpenAnolis小助手15 小时前
一句话看透 JVM,SysOM 诊断 Skill 新增 Java 应用诊断能力
java·开发语言·jvm·阿里云·操作系统控制台·sysom
小七在进步19 小时前
C++入门(2)
java·jvm·c++
zuozong_21 小时前
内存的管理
jvm
SKH.1 天前
网络(5)数据库
jvm·数据库
小七在进步1 天前
C++入门(1)
java·jvm·c++
用户094248568032 天前
第3章:OpenJDK从源码到字节码——Class 文件与 javac 管线
java·jvm
YUJIANYUE2 天前
查立得万用查分电脑版(web环境+查询系统免安装单文件一键运行包)
前端·jvm