jvm

Posted by zangxin on October 8, 2024

JVM

jvm是java程序的运行环境(字节码)

一次编写, 到处运行是jvm的功劳

自动内存管理, 垃圾回收机制

截屏2025-04-22 18.50.17

jvm组成部分

截屏2025-04-22 18.52.09

程序计数器: 线程私有的(线程安全), 内存保存字节码的行号, 记录指令的地址

1
javap -v xxx.class

堆:属于线程共享区域, 主要用于保存对象实例, 数组等, 当堆中没有内存空间可以分配给实例, 也无法扩展时, 则抛出

OutOfMemoryError异常

Java8 内存结构

截屏2025-04-22 22.22.25

年轻代分成三部分: eden区和两个大小相同的survivor区, 根据jvm策略, 经过几次垃圾回收后, 依然存活在survivor区的对俩被移动到老年代区间

老年代: 主要保存生命周期长的对象, 一般是一些老对象

截屏2025-04-22 22.25.10

虚拟机栈:

每个线程在运行时需要的内存就是虚拟机栈(私有的, 线程安全的), 栈先进后出

每个栈由多个栈帧组成, 对应每次方法调用时所占用的内存

每个线程只能有一个活动栈帧, 对应当前执行的方法

垃圾回收主要只的是堆内存, 栈帧弹出后,内存会自动释放

栈内存越大越好吗?

​ 未必, 默认栈内存1024kb

​ 栈帧过大会导致线程数变少

方法内的局部变量是线程安全的吗?

​ 如果方法内局部变量没有逃离方法的作用域范围, 它是线程安全的, 如果局部变量引用了对象, 并逃离了方法的作用范围, 需要考虑线程安全

截屏2025-04-22 22.33.04

什么情况会导致栈内存溢出

栈帧过多导致, 典型: 递归调用

栈帧过大导致内存溢出

堆和栈的区别:

​ 栈内存一般用来存储方法调用和局部变量, 堆内存用来存储java对象和数组. 堆会GC回收, 栈不会

​ 栈内存是线程私有的, 堆是线程共享的

​ 内存溢出报错不同: 栈: stackOverFlowError, 堆: OutOfMemoryError

方法区

是线程共享去区域

主要存储类的信息, 运行时常量池

虚拟机启动时创建, 关闭虚拟机时释放

如果方法区中内存无法满足分配请求, 则会抛出OutOfMemoryError: Metaspace

截屏2025-04-22 22.43.41

常量池

可以看做是一张表, 虚拟机指令根据这张表找到要执行的类名, 方法名, 参数类型,字面量等信息

截屏2025-04-22 22.52.00

运行时常量池

常量池在*.class文件中, 当该类被加载, 他的常量池信息就会放到运行时常量池, 并把里面的符号地址替换成真实地址

直接内存: 不属于jvm内存, 不由jvm管理, 是操作系统内存,常见于NIO操作,用于数据缓冲区, 他的回收成本高, 但读写性能高

截屏2025-04-22 23.03.12

常规IO: 数据缓存了两次

截屏2025-04-22 23.04.03

NIO数据拷贝: 只有一份缓存, 所以效率比较高

类加载器

把字节码文件加载到jvm中, 好让jvm运行字节码

截屏2025-04-22 23.07.23

双亲委派机制

加载某一类时, 先委托上一级的类加载器, 如果类加载器也有上级, 则会继续向上委托, 如果该类委托上级没有被加载, 子类加载器会尝试加载该类

为什么使用双亲委派机制

1.避免某个类被重复加载, 当父类被加载后则无需重复加载, 保证唯一性

2.为了安全, 保存类库的api不会被修改

类装载的执行过程

截屏2025-04-22 23.15.30

加载:

截屏2025-04-22 23.17.30

验证: 验证类是否符合jvm规范, 安全性检查

文件格式,元数据,字节码检查

符号引用验证: 检查常量池中是否存在自己使用到的引用

准备: 为静态变量分配内存,并设置默认值. 静态常量(基本类型)值在该阶段已经确定, 静态常量-引用类型在初始化阶段设置值

解析: 把符号引用转换直接引用, 把符号引用替换成指针直接指向方法(地址)

初始化阶段: 对类的静态变量, 静态代码块执行初始化操作, clinit, 如果父类没有被初始化, 先初始化父类

使用: 调用静态成员, 或者使用new创建对象实例

垃圾回收

什么是垃圾

没有引用指向的对象就是垃圾

如何确定垃圾

​ 引用计数法, 计算对象引用数量多少, 引用为0就是垃圾, 缺点: 解决不了循环引用

​ 可达性分析算法(现在jvm采用的)

截屏2025-04-22 23.31.30

哪些对象作为GCROOT

虚拟机栈中引用的对象

方法区静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

垃圾回收算法

标记清除算法

1.根据可达性分析找出垃圾

2.对这些垃圾进行回收

优点: 速度快

速度: 内存碎片化严重, 数组可能用不了(数组要使用连续的内存)

标记整理算法(老年代经常使用)

回收完, 整理内存

复制算法(年轻代使用)

分成两个区域,每次使用一块, 回收完放复制到另一块内存, 清空另一块

缺点: 占用两块内存

分代收集算法

截屏2025-04-22 23.40.20

新创建的对象都会先放到eden区, 截屏2025-04-22 23.42.54

minorGC, mixedGC, FullGC

截屏2025-04-22 23.45.02

垃圾回收器分类

串行垃圾回收器

截屏2025-04-22 23.48.20

并行垃圾回收器

截屏2025-04-22 23.49.08

CMS(并发)垃圾回收器

截屏2025-04-22 23.49.53

G1垃圾回收器(在jdk9后默认使用)

截屏2025-04-22 23.54.38

Young Collection

截屏2025-04-23 00.03.54

强,软,弱,虚引用

强引用:

只有所有GCRoots对象都不通过强引用引用该对象时, 该对象才能被回收

1
User user = new User()

软引用

截屏2025-04-23 00.07.20

弱引用

截屏2025-04-23 00.08.03

虚引用

截屏2025-04-23 00.10.30

截屏2025-04-23 00.11.01

jvm实践

jvm参数设置

jar包, 在jar启动参数时设置

截屏2025-04-23 00.14.08

war包, 在tomcat的catalina.sh中修改java_ops

截屏2025-04-23 00.13.14

调优就是调整年轻代, 老年代, 元空间的内存空间大小及使用的垃圾回收类型

常用jvm调优参数

截屏2025-04-23 00.16.43

设置堆空间大小

截屏2025-04-23 00.18.20

截屏2025-04-23 00.19.00

虚拟机栈

截屏2025-04-23 00.20.16

截屏2025-04-23 00.21.55

截屏2025-04-23 00.22.42

jvm调优工具

命令工具

jps 当前运行java程序

jstack 查看线程堆栈信息

jmap 生成内存快照

jhat

jstat

截屏2025-04-23 00.30.56

可视化工具

jconsole

visualVM

java内存泄露排查

截屏2025-04-23 07.31.52

cpu飙高排查

使用top命令, 查看cpu占用率,找到PID

再查看是哪一个线程占用

截屏2025-04-23 07.38.17

再使用jstack pid

再从打印的信息中找出对应16进制的线程id的信息, 可以定位具体的代码行数