- Java中有两种对象:Class对象和实例对象
- .class静态文件被JVM加载后,会在堆内存中创建一个全局唯一的Class对象
- 该Class对象中包含了创建实例对象所必需的【元数据和指令】,是创建实例对象的入口
ClassLoader【类加载器】
- 启动类加载器主要负责【核心类库】的加载[ jre/lib/rt.jar和jre/lib/resource.jar等等 ]
- 扩展类加载器主要负责【jre/lib/ext】目录下的jar包和class文件
- 应用程序类加载器主要负责【 当前应用classpath目录】下的所有jar包
- 自定义类加载器主要负责加载开发者自定义的类
加载原则【双亲委派:按照自上而下的顺序进行加载】
第一步:依次向上委派给父类加载器进行加载【避免了类的重复加载且保证核心类库中的类不被破坏】
第二步:加载成功,则直接返回
第三步:加载失败,则委派给子类加载器进行加载,直到最底层的类加载器
加载过程
- 触发创建实例对象的条件:
显示加载:反射。运行期间动态获取类的元信息并调用对象的属性和方法
隐士加载:1、使用new关键字 2、调用静态属性 3、子类加载前会优先触发其父类的加载 - 【本地编译期间】把 java 静态文件编译成 class 静态文件
- 【线上运行期间】将 class 静态文件中的【静态数据结构】加载到【方法区】中的【运行时数据】
- 【线上运行期间】再在【堆】中生成一个该类的【Class对象】作为访问方法区中运行时数据的入口
卸载条件(同时满足以下3个条件)
- 所有实例对象都被回收
- 加载该类的 ClassLoader 被回收
- Class 对象没有被引用
注意:静态变量的生命周期取决于类的生命周期。当Class对象被销毁时,静态变量也会被销毁
【JVM内存模型(1.8)】
堆(内存公有)
传统物理机环境:
初始堆大小(-Xms):物理内存的 1/64
最大堆大小(-Xmx):物理内存的 1/4
服务器一般设置-Xms(最小分配内存)和-Xmx(最大分配内存)相等以避免在每次GC后调整堆的大小
- JVM中最大的一块内存区域,在虚拟机启动时创建,由所有线程共享使用
- 堆分为年轻带和老年代,年轻代:老年代 = 1:2
1、年轻代(Young Generation)【年轻代中Eden:S1:S2的内存分配比例为8:1:1】
1.1、Eden区:新创建的对象首先放在Eden区
1.2、Survivor区:分为From Survivor和To Survivor,存放从Eden区经过垃圾回收后仍然存活的对象
2、老年代(Old Generation):存放长期存活的对象(多次垃圾回收后仍然存活)或者大对象(直接进入老年代)
方法区(内存公有)
- JVM中定义的一种规范,由所有线程共享使用
- JDK1.8对其实现的是本地元空间
类的元数据(Class Metadata):
1、类的完整结构信息(父类、接口、字段、方法等)
2、运行时常量池(Runtime Constant Pool)
3、字段和方法的字节码
4、类加载器引用
常量池
-
静态常量池【class文件常量池】
位置:静态存在class文件中,每个 class 文件对应一个
存储内容:编译期生成的各种字面量和符号引用 -
运行时常量池
位置:方法区的一部分
存储内容:类加载后,静态常量池中的数据进入运行时常量池,并将符号引用转换为直接引用 -
字符串常量池
位置:JDK7之前在方法区,JDK7 及以后移至堆区
存储内容:字符串字面量的引用或实例(遵循享元模式)
栈(内存私有)
- 随着线程的创建而创建,用于存储方法调用的局部变量、操作数栈等信息
- 线程每执行到一个方法时便创建一个栈帧入栈,当方法执行完毕栈帧也随之出栈,所有方法执行完毕后线程也就被释放,栈也就随之释放
本地方法栈
与栈类似,但是服务于本地方法(非Java代码)
程序计数器
每个线程独立拥有,指向当前线程正在执行的字节码指令的地址(如果当前执行的是本地方法,则为空)。
【JVM垃圾.收集算法】
1、标记复制
将内存分为两块,每次只使用一块,GC 时将存活对象复制到另一块,清空原区域,这样做的好处是清理速度快。缺点是:始终有一块内存空间处于空闲状态,适用于新生代(Eden + 2 个 Survivor 区,比例 8:1:1)的垃圾回收
2、标记清除
先标记记存活的对象,再清除未标记的对象,缺点是:清理结束后会产生大量的空间碎片,导致大对象无法分配内存
3、标记整理
先标记存活对象,再将存活对象移向一端,最后再清理边界外内存。无内存碎片,适合存活对象多的老年代
4、分代收集(根据对象存活周期进行分类,采用不同算法)
- 各版本默认收集器
新生代(Young Generation):对象朝生夕灭,使用复制算法
老年代(Old Generation):对象存活久,使用标记-清除或标记-整理
元空间(Metaspace):存储类元数据,GC不频繁
- GC 触发条件
1、Minor GC(新生代 GC):Eden 区满时触发
2、Major GC/Full GC(老年代/整堆GC)
【JVM垃圾.收集器】
- JDK 8:Parallel Scavenge + Parallel Old
- JDK 9+:G1
- JDK 17:ZGC(需手动启用:-XX:+UseZGC)
新生代收集器
- Serial:单线程,STW(Stop The World),适合单 CPU 环境
- ParNew:Serial 多线程版本,与 CMS 配合使用
- Parallel Scavenge【帕-了-乐.斯开-问之】:关注吞吐量,JDK8 默认
老年代收集器
- Serial Old:单线程,标记 - 整理,适合 Client 模式
- Parallel Old:Parallel Scavenge 的老年代版本,多线程标记 - 整理
- CMS:追求低停顿,标记 - 清除算法,分阶段与用户线程并发执行
- G1:面向服务端,将堆划分为多个 Region,动态回收价值最大的 Region
全区域收集器
- ZGC(Z Garbage Collector):JDK 11 引入,超低延迟(<10ms),使用染色指针和读屏障技术
- Shenandoah:与 ZGC 类似,支持并发整理
【JVM参数】
- 配置收集器
# 新生代使用UseParallelGC(吞吐量优先)
-XX:+UseParallelGC
# 老年代使用UseParallelOldGC(吞吐量优先)
-XX:+UseParallelOldGC
# 老年代使用CMS(停顿时间优先)
-XX:+UseConcMarkSweepGC
# 新生代和老年代使用G1GC(停顿时间优先)
-XX:+UseG1GC
# G1最大停顿时间。太小会导致G1跟不上垃圾产生的速度,最终退化成FullGC,所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态
-XX:MaxGCPauseMillis=200ms
- 配置元空间
# 设置原空间大小
-XX:MetaspaceSize=50M
-XX:MaxMetaspaceSize=50M
- 配置堆
# 设置堆大小
-XX:InitialHeapSize=100M
-XX:MaxHeapSize=100M
# 设置年轻代的大小
-XX:NewSize=20M
-XX:MaxNewSize=50M
# 设置老年代大小
-XX:OldSize=50M
# 新老生代的比值。比如-XX:Ratio=4则表示新生代:老年代=1:4,也就是新生代占整个堆内存的1/5
-XX:NewRatio
- 配置栈
# 设置每个线程的堆栈大小(经验值是3000-5000最佳)
-Xss128k
【JVM分析工具】
jps查看应用进程PID
jps -l # 14512 com.snoob.springboot.BffControllerApplication
jmap 查看JVM分配和使用情况
jmap -heap 14512
Heap Configuration:
【备注:初始元空间,替代1.8以前的Permgen】
MetaspaceSize = 21807104 (20.796875MB)
【备注:最大元空间,调整元空间会Full GC,一般将MetaspaceSize和MaxMetaspaceSize设置一样,防止修改时触发FGC】
MaxMetaspaceSize = 17592186044415 MB
MinHeapFreeRatio = 0 【备注:最小堆使用比例】
MaxHeapFreeRatio = 100【备注:最大堆使用比例】
MaxHeapSize = 104857600 (100.0MB) 【备注:最大堆空间】
【备注:表示新生代:老年代=1:2,也就是新生代占整个堆内存的1/3】
NewRatio = 2
【备注:表示Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10】
SurvivorRatio = 8
NewSize = 34603008 (33.0MB)
MaxNewSize = 34603008 (33.0MB)
OldSize = 70254592 (67.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
G1HeapRegionSize = 0 (0.0MB)
【备注:G1垃圾回收算法时,JVM会将Heap空间分隔为若干个Region,该参数用来指定Region的大小】
Heap Usage:
PS Young Generation
Eden Space:
capacity = 17825792 (17.0MB)
used = 14837016 (14.149681091308594MB)
free = 2988776 (2.8503189086914062MB)
83.2334181841682% used
From Space:
capacity = 7864320 (7.5MB)
used = 851968 (0.8125MB)
free = 7012352 (6.6875MB)
10.833333333333334% used
To Space:
capacity = 8388608 (8.0MB)
used = 0 (0.0MB)
free = 8388608 (8.0MB)
0.0% used
PS Old Generation
capacity = 47710208 (45.5MB)
used = 13607144 (12.976783752441406MB)
free = 34103064 (32.523216247558594MB)
28.520403851519575% used
jstat 监控JVM垃圾回收情况
jstat -gcutil PID
S0/S1:幸存者区
E:伊甸园区,初生区
0:老年代
M:元空间
YGC:年轻带回收次数
YGCT:年轻带回收花费时间(秒)
FGC:full GC
FGC:full GC花费时间(秒)
GCT:GC总时间(秒)
jstack 查看指定线程的堆栈信息
top # 查询消耗CPU最多的(进程)PID
top -Hp PID # 根据PID查询消耗(线程)CPU最多的PID
printf '%x\n' PID # 将PID转化成16进制数字2ac
jstack PID | grep 2ac -A 100 # 查询进程号7下的线程运行日志
JVM可视化工具
- jmc.exe
- jconsole.exe
- jvisualvm.exe
JVM性能优化
- JVM调优没有固定的参考参数,需根据不同服务器和系统需求来调整,默认JVM是会自动优化,不到万不得已不建议调整
- 根据项目运行情况,增加适当的堆内存,注意:最小堆内存和最大堆内存设置一致【避免JVM频繁的切换堆内存】
java -jar \
-XX:MetaspaceSize=100M \
-Xms300M \
-Xmx300M \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \ # 设置最大GC停顿时间指标
-XX:InitiatingHeapOccupancyPercent=45 \ # 堆内存使用率大于45%时触发GC
demo.jar