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