JVM
2021-11-05 15:57:36 1 举报
AI智能生成
JVM各种垃圾收集器核心原理整理,JVM调优实战,JVM核心原理整理
作者其他创作
大纲/内容
类加载
类加载器
应用类加载器
扩展类加载器
启动类加载器
加载机制
双亲委托机制
首先请求父类加载,父类再找自己的父类,顶级父类加载不了再向下一级一级加载
加载过程
1.验证
2. 准备
3. 解析
4. 初始化
优化实战
机器4核8G,JVM4G,新生代和老年代各占1.5G
任务
每分钟执行100次数据计算,每次是1万条数据需要10秒
每条数据平均20个字段,平均每条数据1KB
总计算每次任务1万条数据对应10MB
JVM参数
新生代按默认8:1:1分配eden和两块Survivor
Eden区1.2GB,每块Survivor区域100MB
1分钟Eden区满
1GB对象可回收,剩余存活对象200MB
200MB大于Survivor直接进入老年代
8分钟后出发FUll GC
优化
添加新生代内存比例,3GB左右堆内存,2GB分配给新生代,1GB留给老年代
案例背景:每日上亿请求的电商系统
用户
500万日活跃用户
每个用户评价访问20次
下单:10%的付费转化率
50万订单
集中在4小时高峰期内,则平均每秒几十个订单
双十一
每秒1000个订单
机器
3台,每台300个请求
4核8G
每个订单大小:1KB
每秒300kb内存开销
对订单连带对象及其他操作比如订单查询联合估算
每秒 300kb*20*10=60MB内存开销
处理完后全部为垃圾对象
订单对象连带
订单条目
库存
促销
优惠券
内存分配:8G
jvm:4G
堆内存:3G
新生代:1.5G
Eden:1.2GB
Survivor:150MB
Survivor:150MB
老年代:1.5G
永久代:256M
Java虚拟机栈:1M
如果有几百个线程就是几百M
参数设置
-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M
操作系统之类:4G
回收
每秒60MB,Eden1.2G,20秒MInorGC
Minor GC可能有100MB订单在处理,所以100MB对象存活
进入S1
下次回收
100MB进入s2
大于150MB
影响
对象频繁进入老年代
同龄对象超过Survivor空间50%
影响
对象频繁进入老年代
调优
JVM
新生代:2G
Eden:1.6G
Survivor:200MB
Survivor:200MB
老年代1G
参数
问题
Survivor不够
对象频繁进入老年代
几分钟不会收的对象一般为@service,@Controller,所以需要降低MaxTenuringThreshold
大对象问题
1M就够了
-XX:PretenureSizeThreshold=1M
垃圾回收器
新生代:ParNew
-XX:+UseParNewGC
老年代:CMS
-XX:+UseConcMarkSweepGC
总结
每秒占用多少内存
多长时间触发一次Minor GC
一般Minor GC后有多少存货对象
Survivor能放下吗
会不会频繁因为SUrvivor放不下导致对象进入老年代
会不会因动态年龄判断规则进入老年代
对象计算方式
对象头
对象基本信息及class指针相关信息占用64bit
实例数据
对象填充
每秒10W QPS交友APP
频繁Full GC
原因
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=5
5次full gc后才整理内存碎片
优化
-XX:CMSFullGCsBeforeCompaction=0
新手工程师瞎调JVM参数
频繁Full GC
原因分析
gc日志中 有Metadata GC Threshold
1.8 Metadata元数据区导致
呈现波动状态,不听有类加载到Metaspace
追踪类加载
-XX:TraceClassLoading
追踪类卸载
-XX:TraceClassUnloading
优化
线上每天10次full gc
参数设置
-XX:SurvivorRatio=5
E:F:T=5:1:1
大概E 365M S 70MB
-Xms1536M -Xmx1536M -Xmn 512M -Xss256K
-XX:CMSInitiatingOccupancyFraction =68
老年代占比68触发full gc
gc日志分析
每分钟3次Young gc
20s Eden满
每秒 15-20MB
一次gc时间 50ms
1小时2次Full gc
30分钟一次full gc
600MB左右触发
是否因为动态年龄判断
偶尔一次几十M对象进入老年代
突然老年代新增几百MB对象
大对象
分析大对象由来
jstat
jmap
导出dump内存快照
大Map
select * from 没有带where条件
System.gc引发的full g
平时正常,这次直接卡死
gc日志分析
年轻代增长很慢,老年代才用不到10%,永久代用了20%
代码中使用了System.gc
优化工具
jstat
jstat -gc PID 5000
相关参数解释
S0C
From Survivor区大小
S1C
To Survivor区大小
S0U
From Survivor 区当前使用的内存大小
S1U
To Survivor区当前使用的内存大小
EC
Eden区大小
EU
Eden区当前使用的内存大小
OC
老年代大小
OU
老年代当前使用的内存大小
MC
方法区(永久代、元数据区)大小
MU
方法区(永久代、元数据区)当前使用的内存大小
YGC
系统运行迄今为止的Young GC次数
YGCT
Young GC的耗时
FGC
系统运行迄今为止的Full GC次数
FGCT
Full GC耗时
GCT
所有GC的总耗时
重点关注参数
新生代对象增长速度
jstat -gc PID 1000 10
Young GC触发频率
通过Eden区大小和每秒新增对象推断
Young GC耗时
Yong GC次数/Yong GC总耗时
Young GC后存活对象
知道多长时间Young GC则可调大jstat -gc时间观察每次gc后Eden、Survivor内存大小
Young GC后进入老年代的对象大小
老年代对象增长速率
Full GC触发频率
Full GC耗时
Full GC次数/Full GC总耗时
jstat -gccapacity PID
堆内存分析
jstat -gcnew PID
年轻代GC分析,TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄
jstat -gcnewcapacity PID
年轻代内存分析
jstat -gcold PID
老年代内存GC分析
jstat -gcoldcapacity PID
老年代内存分析
jstat -gcmetacapacity PID
元数据区你内存分析
map
内存泄漏分析工具
下载
https://www.eclipse.org/mat/downloads.php
MemoryAnalyzer.ini
修改JVM内存大小,看dump多大就修改多大
使用 Leak Suspects
内存分析
jmap
jmap -heap PID
jmap -histo PID
打印出对象占用空间大小按降序排序
jmap -dump:live,format=b,file=dump.hprof PID
在当前目录下生成一个dump.hrpof文件
jhat dump.hprof -port 7000
内置web服务器图形化分析堆转储快照
JVM监控
Zabbix
OpenFalcon
Ganglia
OOM
永久代OOM
原因
1. 线上不对Metaspace区域设置大小,使用默认值
2. 大量运用cglib之类的技术动态生成类,没有控制好
解决
1. 第一种情况,通常设置为512MB就够了
-XX:MetaspaceSize=512m
-XX:MaxMetaspaceSize=512m
-XX:MaxMetaspaceSize=512m
GC
垃圾回收算法
复制算法
适用范围
新生代
原理
将新生代划分为2块内存,只使用一块,待这个内存满了,把里面的对象清除到另一快内存
优点
没有内存碎片
缺点
内存只能使用一半
标记清清除算法
原理
标记处可以回收的垃圾对象,然后直接回收
缺点
会产生内存碎片,浪费内存
优化复制算法
优化
将新生代分为1个eden和2survivor
原理
平时使用eden和一个survivor,垃圾回收将存活对象转移到另一个survivor
标记整理算法
原理
将存活对象标记,然后整理移动到一边,使存活对象内存紧凑,避免出现内存碎片
什么对象被回收
引用
强引用
垃圾回收的时候绝对不会被回收
软引用
正常垃圾回收是不会回收软引用对象,如果垃圾回收后内侧空间还是不够存放新对象,则会把软引用对象回收
弱引用
垃圾回收一定会回收的对象
虚引用
垃圾对象算法
可达性算法GC Roots
没有GC Roots引用的对象
调用finalize()方法,是否把自己这个实例对象给某个GC Roots变量,如果有,则不回收
引用计数
循环应用可通过Recycler算法解决,但在多线程环境下效率低下
什么对象进入老年代
年龄达到15,即躲过15次GC
通过JVM参数: -XX:MaxTenuringThreshold 设置,默认15
动态年龄判断
一批对象年龄的总大小大于这块Survivor区域内存的50%,这批对象最大年龄对象直接进入老年代
大对象直接进入老年代
通过参数: -XX:PretenureSizeThreshold
Minor GC存活对象过多无法放入另一个Survivor,这些对象直接进入老年代
GC分类
Minor GC
老年代空间分配担保规则
执行Minor GC前,JVM检查老年代可用空间,是否大于新生代所有对象总大小
大于
发起Minor GC
小于
参数: -XX:-HandlePromotionFailure
设置了此参数
老年代剩余内存是否大于Minor GC每次进入老年对象的平均大小
大于
进行Minor GC
剩余存活对象
小于Survivor区大小
存活对象进入Survivor
大于Survivor区大小
小于老年代可用内存大小
进入老年代
大于老年代可用内存大小
触发Full GC
重新Minor GC
如果老年代还是没有足够空间存放Minor GC过剩对象
OOM
小于
进行full gc
未设置此参数
直接触发Full GC
重新执行Minor GC
JDK1.6后被废弃,无需设置
触发条件
Eden区要满了
代码模拟
full GC
触发条件
-XX:CMSInitiatingOccupancyFaction
老年代占比多少触发CMS垃圾回收
JDK1.6默认92%
老年代可用内存小于新生代全部对象大小,没有开启空间担保参数
老年代可用内存小于历次新生代GC后进入老年代的平均对象大小
新生代Minor GC后存活对象大于Survivor,进入老年代,此时老年代内存不足
垃圾回收器
Serial
使用范围
新生代使用
单核CPU操作系统
线程
单线程收集
Serial Old
使用范围
老年代使用
ParNew
使用范围
新生代使用
多核CPU操作系统
收集算法
复制算法
线程
多线程垃圾回收
选择
没有G1主流的新生代垃圾收集器
设置新生代使用
-XX:+UseParNewGC
收集线程数
默认和CPU核数保持一致,即4核回收线程为4个线程
设置线程数
一般使用默认不设置
-XX:ParallelGCThreads
是否stop word
是
CMS
使用范围
老年代使用
收集算法
标记清理算法
回收过程
1.初始标记
是否Stop the World
是
作用
标记出所有的GC Roots直接引用的对象
耗时
很少
原因
仅仅标记GC Roots直接引用的对象
2.并发标记
是否Stop the World
否
会继续创建新的存活对象,也可能让部分存活对象失去引用
作用
对已有的对象进行GC Roots追踪
耗时
最久
原因
需要追踪所有对象是否从根源上被GC Roots引用
3.重新标记
是否Stop the World
是
作用
标记二阶段新增存活对象和垃圾对象
耗时
很少
原因
标记二阶段程序运行改变的少数对象
4.并发清除
是否Stop the World
否
作用
清理标记出来的垃圾对象
耗时
很久
原因
需要清理对象
5.碎片整理
设置
-XX:+UseCMSCompactAtFullCollection
默认0
每次Full GC后进行一次内存整理
是否Stop the World
是
缺点
耗用CPU
CMS默认垃圾回收线程数量:(CPU核数 + 3) / 4
浮动垃圾
并发清除阶段系统继续运行,然后一些对象进入老年代,同时又变成垃圾对象
CMS只会回收之前标记出来的垃圾对象
需要下次GC才能回收
Concurrent Mode Failure
原因
并发清除时系统再次产生的对象进入老年代,老年代内存不够
问题
自动使用Serial Old替换CMS,即强行Stop the World
G1(Garbage First)
使用范围
统一收集新生代和老年代
实现算法
整体看基于“标记-整理”算法
从局部(两个Region之间)上看又是基于“标记-复制”算法
选择
大内存使用G1
解释
将java堆内存拆分为大小相等的Region
新生代和老年代都是逻辑概念
每个Region可能是新生代,也可能是老年代
优缺点
优点
可以设置垃圾回收的预期停顿时间
即Stop the World时间
如何控制系统停顿时间
对每个Region进行追踪判断有多少垃圾对象,需要多少时间回收
缺点
卡表实现比CMS复杂,耗费更多内存
参数设置
设置垃圾回收的预期停顿时间
-XX:MaxGCPauseMills
默认200ms
使用G1
-XX:+UseG1GC
设置每个Region大小
-XX:G1HeapRegionSize
取值范围:1MB~32MB,且应为2的N次幂
Region
大小
最多2048个
必须是2的倍数,比如1MB,2MB,4MB
保持默认设置即可
手动方式
-XX:G1HeapRegionSize
计算
比如堆内存为4G,即4096/2048,每个Region大小为2MB
新生代
初始默认为5%
运行不断增加
即200MB内存,100个Region
80个Region是Eden
两个Survivor各占10个Region
不会超过堆内存60%
设置
-XX:G1MaxNewSizePercent
是否有Eden和Survivor概念
有
老年代
40%
改动
大对象
不直接进入老年代,有专门存放大对象的Region
判断
对象超过一个Region大小的50%
存放
是否大于单个Region
小于
存放在一个Region
大于
横跨多个Region来存放
新生代60%,老年代40%,如何存放
比如1200个Region属于新生代,如果垃圾回收后空出1000个Region,那么这1000个Region就不属于新生代,可以用来存放大对象
新生代+老年代混合垃圾回收
参数
-XX:InitiatingHeapOccupancyPercent
默认45%
触发机制
老年代堆内存占45%的Region
回收过程
minor GC
触发条件
新生代Region占堆内存60%
回收过程
与ParNew基本一致
设置停顿时间
-XX:MaxGCPauseMills
默认200ms
Mixed新生代+老年代混合垃圾回收
参数
-XX:InitiatingHeapOccupancyPercent
默认45%
触发机制
老年代堆内存占45%的Region
具体过程
参考CMS
改动
最后混合回收可以设置回收次数
参数
-XX:G1MixedGCCountTarget
默认8次
解释
回收一部分Region,系统恢复运行,然后再停止系统再回收一分部分Region
优点
减少系统卡顿时间
回收内存超过堆内存5%立即停止混合回收
参数
-XX:G1HeapWastePercent
默认5%
复制算法基于Region回收,不会出现内存碎片问题,进行内存碎片整理
回收的Region存活对象低于85%才能回收
参数
-XX:G1MixedGCliveThresholdPercent
默认85%
回收失败问题
Stop world,单线程标记、清理、压缩整理,空间出一批Region
问题
拷贝过程没有空闲Region
如何尽量减少Mixed GC
让垃圾对象尽量在新生代被回收
设置合理的Eden和Survivor大小
老年代设置为较小值
问题
长期存活对象多容易OOM
提高InitiatingHeapOccupancyPercent的值
问题
增加了并发标记阶段计算负担和MixedGC阶段计算和预估的负担
不适合CPU负载较高的计算型业务系统
Shenandoah
ZGC
Epsilon
Stop the World
内存分布
线程共享
方法区
1.8做了优化变成了元数据区
堆
线程独有
本地方法栈
java虚拟机栈
程序计数器
JVM参数设置
(年轻代)新生代
初始新生代大小
-Xmn3000m
最大新生代大小
-XX:MaxNewSize=5242880
5MB
此处的大小是(eden+ 2 survivor space)
堆
初始化堆大小
-XX:InitialHeapSize=1048576
10MB
-Xms512m
最大堆大小
-XX:MaxHeapSize=1048576
10MB
-Xmx512m
永久代(jdk1.8后已移除)
初始大小
-XX:PermSize
最大大小
-XX:MaxPermSize
元数据区(>=1.8)
设置类元数据区的最大大小
-XX:MaxMetaspaceSize=512m
设置类元数据区的初始大小
-XX:MetaspaceSize=512m
每个线程的堆栈大小
-Xss512k
大对象阈值
-XX:PretenureSizeThreshold=10485760
10MB
年轻代使用ParNew
-XX:+UseParNewGC
老年代使用CMS
-XX:+UseConcMarkSweepGC
老年代占比多少full gc
-XX:CMSInitiatingOccupancyFraction=92
老年代占比95%触发full gc
碎片整理
-XX:+UseCMSCompactAtFullCollection
每次Full GC后进行一次内存整理
1.6版本后默认开启,不必显式设置,1.9被废弃
每次full gc整理内存碎片
-XX:CMSFullGCsBeforeCompaction=5
每次 full gc 整理内存碎片
CMS GC remark之前做一次YGC
CMSScavengeBeforeRemark
打印详细gc日志
-XX:+PrintGCDetails
打印每次GC发生的时间
-XX:+PrintGCTimeStamps
将gc日志写入磁盘
-Xloggc:gc.log
年龄多少进入老年代
-XX:MaxTenuringThreshold=15
追踪类加载
-XX:TraceClassLoading
追踪类卸载
-XX:TraceClassUnloading
Eden、Survivor比
-XX:SurvivorRatio=5
Eden:Survivor:Survivor2 = 5:1:1
禁止调用System.gc();
-XX:-DisableExplicitGC
0 条评论
下一页