JVM - 虚拟机

目录

1. JVM组成原理

Pasted image 20260324210858

1.1 程序计数器

  • 每个线程都私有一份PC
  • 内部保存字节码的行号, 用于记录正在执行的字节码指令的地址

1.2 堆 —–>实例/数组

  • 在线程共享区, 主要用来存储对象实例、数组等
  • 分为年轻代、老年代
  • 年轻代分为Eden、S0、S1 (8:1:1)
Pasted image 20260324212336 ### java 7/8的变化 Pasted image 20260324212325

java7中堆的方法区/永久代(存储类信息)
被移动到本地内存并且改名为元空间, 就是防止后续类太多导致OOM

1.3 虚拟机栈 —–>运行时内存

每个线程运行时所需要的内存,称为虚拟机栈每个栈由多个栈帧 (frame) 组成,
对应着每次方法调用时, 所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

  • 一个栈帧一般为1024K(1M)
  • 方法内的局部变量是线程安全的, 每个线程有一个局部变量表
Pasted image 20260324213615

线程安全?

没暴露出来 -> 安全

1
2
3
public static void method(){
String str=new String("666"); //完全的线程私有, 在局部变量表中
}

作为参数(传递or返回) -> 不安全

1
2
3
4
5
6
7
8
public static void method(String str){ //在传递过程中被线程共享, 可能修改
...
}

public static void method1(){
List<Integer>ls=new ArrayList<>();
return ls; //返回时被线程共享, 可能修改, 逃逸分析
}

栈内存溢出

  • 栈帧过多导致内存溢出 (递归调用不当)
  • 栈帧过大导致内存溢出

垃圾回收

垃圾回收是否涉及栈内存?
垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放, 不用GC回收

栈内存越大越好?

内存一定,栈内存分配越大,栈数就越少;但单个栈内可容纳的栈帧就越多

堆栈的区别

Pasted image 20260324215342

1.4 方法区 ——>类/常量

  • 线程共享, 大小默认无上限
  • 类信息+运行时常量池
  • jvm启动时创建, 关闭时释放
  • 如果方法区域中的内存无法满足分配请求,则会抛出 OutOfMemoryError: Metaspace
Pasted image 20260324213104

常量池

  • 可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
Pasted image 20260324222330

1.5 直接内存 ——>I/O数据

  • 堆外内存, 虚拟机的系统内存
  • 由 Java 通过 NIO 等方式申请使用,不属于 JVM 规范中的运行时数据区。
  • 分配回收成本较高, 读写性能好, 不受JVM内存回收管理
  • 直接内存本质上就是一块由Java 向操作系统申请的本地内存
  • ByteBuffer 这类堆外缓冲区、I/O 传输数据

常规IO –> 需要拷贝

Pasted image 20260325150836

NIO –> 直接访问

Pasted image 20260325151025

2. 类加载器

2.1 what & how

  • 找类 : 去磁盘、classpath、jar 包里找 .class
  • 读字节码 : 把二进制字节流读进来
  • 把它交给 JVM, 最终变成 JVM 能识别的 Class<?>
Pasted image 20260325152625 Pasted image 20260325153023

2.1.1 双亲委派机制

加载一个类时, JVM会先层层委托上一级类加载器先加载, 如果上级不能加载再往下传递

好处

  • 父类加载后则无需重复加载, 避免类被重复加载
  • 保证类库的API不会被修改
Pasted image 20260325153923

2.2 类装载流程

Pasted image 20260325154453

加载:查找和导入 class 文件。通过类的全限定名找到对应字节流(来源可以是磁盘、jar 包、网络、动态生成),解析成方法区的运行时数据结构,并在堆上生成对应的 java.lang.Class 对象作为访问入口。类加载器遵循双亲委派机制执行此过程。

验证:保证加载类的准确性。

分四个子阶段依次进行:
文件格式验证(魔数 0xCAFEBABE、版本号)、元数据验证(继承关系合法性)、
字节码验证(数据流与控制流合法性,最复杂)、符号引用验证(引用目标存在且有访问权限)。
任一环节不通过抛 VerifyError

准备:为类变量(static 变量) , 分配内存并设置初始值

注意这里赋的是零值(int0,引用类型为 null),不是你写的初始值。
例外:static final 编译期常量直接在此阶段赋真实值。真正的赋值逻辑要到初始化阶段的 <clinit> 里才执行

解析:把类中的符号引用转换为直接引用。

符号引用是常量池里的一段字符串描述(如 "com/example/User"),直接引用是真实的内存地址或偏移量。解析可以在初始化之后懒执行(动态解析),支撑了 Java 的多态和动态绑定。

初始化:对类的静态变量、静态代码块执行初始化操作。

JVM 执行编译器自动生成的 <clinit>() 方法,该方法将所有 static 变量赋值语句和 static {} 代码块按源码顺序合并执行。父类的 <clinit> 先于子类执行,JVM 加锁保证有且仅执行一次,天然线程安全。只有”主动使用”(new、访问 static 字段/方法、反射、子类触发、JVM 启动主类)才触发此阶段。

使用:JVM 开始从入口方法开始执行用户的程序代码。类已完全就绪,对象可以被正常实例化(此时执行 <init>() 即构造方法)、方法可以被调用、字段可以被访问。JIT 编译器会在此阶段对热点代码进行即时编译优化。

卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象。

触发条件非常苛刻:该类的所有实例都已被 GC 回收、加载该类的 ClassLoader 已被回收、该类的 Class 对象没有任何地方被引用。Bootstrap 加载的核心类(StringObject 等)永远不会被卸载,只有用户自定义 ClassLoader 加载的类才有可能被卸载。

Pasted image 20260325170259

3. 垃圾回收

垃圾: 某个对象没有任何引用指向它, 说明它没用了, 会被回收

3.1 回收时

引用计数法

添加一个字段ref, 统计指向它的引用, 一旦ref为0, 就会被回收

循环引用

a.next=b;
b.next=a;

这样即使没有别的引用指向a/b, 二者互相引用, 也会导致无法被回收, 从而引起OOM

可达性分析算法

GC Root -> 局部变量/static/final static/native

Pasted image 20260325175321

凡是从GC Root出发能够到达的对象都是存活的对象, 其他将会被回收

3.2 垃圾回收算法

新生代对象死亡率极高,用复制算法一次 Minor GC 就解决;
老年代对象相对稳定,用整理算法保证大对象有连续空间。

标记-清除

Pasted image 20260325180359

标记-复制 (teen)

Pasted image 20260325180436

标记-整理 (old)

Pasted image 20260325180448

3.3 分代垃圾回收

把堆按对象生命周期分区分为年轻代+老年代

  • 将年轻代分为Eden+S0+S1
  • 老年代 : 年轻代 为 2 : 1
Pasted image 20260325191440

Minor GC

  • 发生在年轻代
  • STW时间更短
  • 标记Eden+From, 复制到To

Mixed GC

  • 发送在年轻代+部分老年代
  • G1持有

Full GC

  • 发生在整个堆
  • STW时间更长, 应尽量避免

3.4 垃圾回收器

按age分

Pasted image 20260325201431

按type分

Pasted image 20260325201740

CMS

并发, 标记-清除老年代

Pasted image 20260325203526

G1

分Region
Garbage First
短STW
Mixed GC
并发标记

Pasted image 20260325203025 Pasted image 20260325204637

1. 初始标记 Initial Mark

  • 短暂停顿 STW
  • 先把从 GC Roots 直接能摸到的活对象记下来

2. 并发标记 Concurrent Mark

  • 和用户线程一起跑
  • 把整个堆里活对象关系大致摸清楚

3. 最终标记 Remark

  • 短暂停顿 STW
  • 修正并发阶段漏掉的那点引用变化

4. 筛选回收 Evacuation / Cleanup

  • 挑出回收收益高的 Region
  • 把存活对象复制到新 Region
  • 老 Region 直接腾空回收

四大引用

Pasted image 20260326092721

强引用 (有则GC永不回收)

static / final static / local / native

Pasted image 20260326092005

软引用 (看内存)

Pasted image 20260326092028

弱引用 (GC就回收)

Pasted image 20260326092054

虚引用 (get不到, 只通知)

Pasted image 20260326092111

JVM实践

调优的本质是在这几个维度做取舍:

  • 内存大小(给多少堆)
  • GC 收集器选择(选什么算法)
  • 停顿时间目标(能忍多久 STW)
  • 线程栈大小(多少线程能跑)。

调优参数?

参数维度

Pasted image 20260326094544

参数列表

Pasted image 20260326100820

调优工具

Pasted image 20260326100906

内存泄漏

Pasted image 20260326100954

CPU

Pasted image 20260326101026