jvm调优
2024-05-08 09:54:11 1 举报
jvm内存模型与jvm调优。这个破软件总是丢数据,当前这个版本丢失了近一个月的修改量,主题还在部分细节存在缺失和错误,暂时不修改了。有问题可联系,长期在线立即回复,除非不在线。
作者其他创作
大纲/内容
-XX:+UseCMSCompactAtFullCollection:FGC前先对老年代做压缩整理(减少碎片)-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0代表每次FullGC后都会压缩一次,根据CMS启动频率估算,CMS比较频繁配置3-5,不频繁默认0即可
JIT即时编译器
unused1bit
达到优化条件
栈上分配对象
H
.java
STW
未使用16位
invokeDynamic
compacting
StackFrame
没有可拷贝/可晋升的空闲region
空转发表
1-n:方法入参
a1
Lookup=MethodHandles.lookup()获取调用上写文,用来做访问权限:在哪建lookup就有哪的权限,例如在类内部创建lookup那么就可以在外部执行该类的私有方法
Pause Remark重新标记:STW标记并发标记过程中漏标的对象STAB。如果整个region没有活跃对象,那么整体回收。STW时长不确定,直到完成
父类成员属性(存在父类Object除外)引用变量4字节(未开启指针压缩8字节),基础类型按实际(如long占8字节,Long占4字节)
f
M0实际指向转移前位置
线程死亡同步回收
a
e1
invokeDynamic:在执行时创建调用点Callsite,在这个调用点,查找1调用方法的对象的Class 2查找返回值类型,入参类型。静态连接:1和2编译时确定动态连接:1运行时确定,2编译时确定invlkeDynamic:1和2运行时确定,类似反射
老年代ParallelOld实际老年代由FGC回收
新regionN
#3->addr3
NUMA
y
读写屏障:load和store命令执行前后插入代码,改变引用关系前后做一些处理,例如记录卡页卡表,改变对象颜色
E
漏标
断开连接XXX
跨代引用问题CardMarking:为了避免YGC扫描整个老年代(FGC全堆扫描不用卡表,CMS/MixedGC借用YGC后survivor为根)带来的标记性能开销,通过增加空间rememberset(rset,底层靠cardTable和cardPage实现)记录老年代引用年轻代的对象,用空间换时间。
类元信息Klass
老年代压缩算法性能较差,STW长
静态链接
1转移对象
调优方法与参数
card1
aload1
老年代够分配新对象
Large Region>4M 存>4M
转移:将对象拷贝到新的页,不会执行重定位,此时引用地址还在原位没变,当有业务线程访问堆内的对象时发现指针颜色是M0/M1,那么发起重定位流程:查找转发表找到转移后的地址,并修改引用地址,这个过程由业务线程完成,这会降低吞吐量。新创建的对象放到新页和不参与回收的页
bindTo方法句柄关联执行对象,执行关联方法
M0
运行时常量池KCLASS:加载到方法区的常量池-将符号引用(#1)转变成直接引用(即内存地址).注意数组结构且从0开始,0表示null。反编译javap -verbose -p XXX.class
主动触发规则Proactive
d
多线程垃圾收集器(jdk8默认):YGC->FGC适合多核cpu+4G以下内存:用更多的GC线程提高回收效率,吞吐量极高,回收效率比CMS高10%-15%,但STW相对较长,往往需要1S以上。-XX:MaxGCPauseMillis最大STW时间/-XX:GCTimeRatio吞吐量设置垃圾收集时间占总时间的比率,这两参数是通过扩缩新生代空间实现的。如SWT短那么分配空间小,触发YGC就会频繁
6并发转移完成
对照rset重定向a
region0
可满足指定停顿时间要求的垃圾收集器G1(GarbageFirst,-XX:+UseG1GC,JDK9默认):适合至少4核至少8G内存以上,最大32G。性能YGC->Mixed GC->FGC1.G1可以满足用户设置的停顿时间,如设置STW时限-XX:MaxGCPauseMillis默认200ms,G1会按照停顿时间动态调整年轻代大小,同时,MixedGC分段回收也会让每个分段的回收在这个时限内。相比CMS的STW不确定性可能更适合程序员2.随着硬件越来越便宜,庞大的内存导致一次性全堆回收耗时必然很久,这会导致GC并发失败概率提高进而FGC次数增加,为此G1将堆分而治之划分为2048个小格region,每个region从1M-32M最小1M,如果分配内存较小如100M那么划分region为100个,其代表一种区域且不要求连续,回收时向空region复制(H使用清除算法回收)回收速度又不产生空间碎片,根据STW时限筛选出部分高回收价值region,在时限内执行回收,然后交给用户线程,如果还有未回收完的,那么下次YGC时再回收一部分后再交给用户线程,串行交替运行,这降低了吞吐量(比CMS低10%)且牺牲了一定内存空间(每个region都有rset,每个card只存一个对象)所以G1只适合大内存。3.每个region分为不同角色有:空闲/E/S/O/H(H存放大对象-超过region一半,如太大跨多个连续region存放,找不到FGC),且每种角色所属空间不一定连续,region角色会动态变化如E->O->S。G1根据GC情况动态调整角色占比,这种特性降低了人工调优的难度,不用像ParallelGc/CMS那样调整年轻代在堆空间占比以避免出现FGC的问题4.G1每个Region都有CardTable,用来解决O指向E/跨代引用问题和O指向O部分收集需要重定位,CMS只有一个CardTable缺点:1当内存太大时(超过100G),无法在停顿时间内完成,出现STW超长的问题(MaxGCPauseMillisz只是预计) 2、YGC会打断MixedGC过程,一旦频繁发送,MixedGC将得不到执行,那意味着老年代没人回收,最终空间耗尽触发FGC。3、越来越多的应用场景不能接受STW,G1的STW没有减少只是拆分,甚至总STW比ParallelGC都长,因此需要一款几乎没有STW的垃圾回收器:ZGC
...
G1通过Rset解决:1.O指向E/S/H:解决跨代引用问题,YGC时不用扫描O2.O指向O:G1采用分段回收,指向移动对象的引用可能不参与本次回收,重定向时用rset指示修改哪个引用
MethodArea:位于Matespace区,可以动态扩缩容,-XX:MetaspaceSize设置方法区初始空间,占满后触发Matespace GC,仍不足那么扩容到-XX:MaxMetaspaceSize,还不足FGC
空闲列表
card0512Byte
STW压缩碎片
用户线程
局部变量表
bootstrap:jre.lib
Pause Young (Mixed):mixedgc开启多次筛选回收,与业务线程交替执行,-XX:G1MixedGCCountTarget默认8次,每次回收控制在指定STW时限-XX:MaxGCPauseMillis,直到所有region回收完/可回收垃圾占比低于G1HeapWastePercent默认整堆5%/达到次数上限
e
1触发FGC阈值:-老年代不足以存放YGC后存活的所有对象-老年代不足以存放历次进入老年代存活对象的平均值2触发CMS阈值:CMSInitiatingOccupancyFraction=92%3触发G1阈值:InitiatingHeapOccupancyPercent=45%
初始标记Initial Mark:找老年代的根(根可达),极快
1发现不是remapped
Mutator线程就是业务线程
Constant_Class_info
更新rset引用
浮动垃圾
2并发标记开始前
.scala
invoke/invokeExact
从年轻代转移到老年代条件:1.分代年龄:经过-XX:MaxTenuringThreshold次(默认15,CMS默认6)YGC仍存活的对象2.动态年龄:s0/s1空间占比达到-XX:TargetSurvivorRatio默认50%以上,将超出的高龄对象移动到老年代
ConcurrentMarkCycle过程发生YGC,会让MixedGC无法启动,导致老年代无人收集,多次出现这种情况只能等待FGC清理。
KlassPointer4字节
savepoint
已加载的类缓存
标记-整理(压缩):标记所有存活对象,将存活的对象随机向一端移动。jvm使用滑动整理lisp2:标记存活对象,再一遍全堆扫描在存活对象forward上记录移动后的位置,再全堆扫描存活对象发现如果对象引用了移动前的地址,那用原位置的forward重新赋值引用地址(先重定向再移动对象避免覆盖问题),再全堆扫描将存活对象移动到forward位置,最后清除边界外的内存。这是最完整回收内存的算法,但因为扫描全堆三遍也是最慢的一种算法。既没有复制算法的空间浪费,又整理好了内存,因此所有垃圾回收器FGC都采用这个算法
1
运行时将一条JVM指令翻译成一条或多条适合特定OS的C++代码,逐条执行
业务执行,E耗尽,触发YGC
本地方法栈(与操作数栈一样,只是给native方法专用的)
c
S
g
Generational ZGC
结果
跨代引用:region是E/S时,key+value对象为根
#5->addr5
invokeVirtual实例方法
是:用YGC存活对象为根
Mark From Roots并发标记:三色标记,与用户线程并行,标记整个老年代的存活对象。这块占80%的GC时间
注意:这时的e其实已经不存在了,可能是空,也可能被其他对象占用,只是指向e的d还指向这个位置
编译器(如javac等)
父类委托
创建主线程和其线程栈
颜色指针
2写转发表
新new的对象1放到新region里2状态remapped
ZGC:一次回收全程STW控制在1MS-10MS(解决GC痛点:之前没有低STW的,CMS的YGC STW无可避免),最大支持16TB内存,吞吐量降低15%以内,必须使用大内存1将内存动态分页:Small Region:容量固定为 2M, 存放小于 256K 的对象;Medium Region:容量固定为 32M,放置大于等于256K但小于4M的对象;Large Region: 容量不固定可动态调整,可以动态变化,但必须为2MB 的整数倍,用于放置 4MB或以上的大对象。相比G1,region不限制个数大小还可动态调整2不分代,没有年轻代大量存活复制算法慢的问题+没有跨代引用rememberset占用空间问题+不用写屏障。代价是每次ZGC都要扫描全堆,使得垃圾回收周期漫长,回收过程与用户线程并行更久导致浮动垃圾比CMS/G1多得多,导致ZGC容易出现内存不足而进入FGC长时间STW状况,因此ZGC最重要的调优点是选择合适的ZGC的触发时机(ZGC自身会调优触发时间,需要我们做的是选择触发模式);虽然没有分代,但标记后识别出可回收率低的region不会被选中参与回收,因此长活对象经过几次GC后就会慢慢堆积到固定的几个region,这也等于老年代了3使用颜色指针、转发表+读屏障解决对象转移和重定位不能与业务线程并发的难题缺点:颜色指针要求引用地址必须64位,且不能开启指针压缩,因此ZGC比其他gc更消耗内存
Pause Young (Prepare Mixed):Concurrent Mark Cycle完成后触发的YGC,额外会按停顿时间选出回收效率最高的某些数量O/H (不可回收对象达到G1MixedGCLiveThresholdPercent以上不参与回收,默认85%);使用复制算法将存活对象拷贝到空闲region;因移动了对象,引用地址发生变化,用rest做O重定位;下次回收剩余的。Prepare Mixed表明其是首个分段Mixed回收而已
Marked11位
O
hashcode31bit
关联Class+方法名+出入参类型
类成员属性引用变量4字节(未开启指针压缩8字节),基础类型按实际(如long占8字节,Long占4字节)属性排列顺序可能与定义时的顺序不同,jvm会对属性重新排序,long/double需要排在8N的位置
d1
b
随堆内持有对象回收而回收,一般应手动回收
ext::jre.lib.ext
并行过程中触发YGC/FGC会终止MixedGC
转发表
自定义classloaderGC Root
remapped
将频繁执行的热点代码编译成C++实现的本地方法,不用解释器去解释指令执行从而提高性能。jvm支持所有代码都编译-Xcomp执行加快一些速度,但这样会导致启动很慢
card3
Padding为对象整个空间(红+绿+蓝)补足8字节的整数倍
0
字符串常量池
并发标记Concurrent Mark: 三色标记,与用户线程并行,只标记老年代的对象。这块占80%的GC时间.-XX:ConcGCThreads可以设置并发标记和并发清理开启的GC线程数
多次FGC无法获得足够容纳待分配空间时
s1
可中断并发预处理阶段:并行标记年轻代引用老年代的对象(ParallelGC没有OldGC,FGC没有这个问题),减少下一阶段重新标记STW的工作量。当Eden>2M触发本阶段,Eden>50%被中断,如果本过程中发生YGC,那么只需扫描survivor无需扫描整个新生代了,因此会等待5S看会不会发生YGC,实在没触发YGC只好自己去扫描整个年轻代了,因为不能一直等,这个阶段太久浮动垃圾会很多容易发生FGC,所以这个阶段不会很久,能干完最好干不完让重新标记STW再干。。那如果开启CMSScavengeBeforeRemark本过程会先来一发YGC,再去标记老年代,这样就会缩短重新标记STW时间,还能降低后续再出现YGC的概率,避免连续STW
老年代SerialOld实际老年代由FGC回收
CardPage512Byte
针对1原始快照STAB(G1使用):当灰色对象删除白色对象的引用关系时,重新扫描a。缺点:如果白色对象a没有存活,那么多扫描了a,此时的a为多标G1采用这种方式,是因为c可能在别的region,如果采用增量更新,G1需要扫描其他region才能确认本region的引用关系+G1使用STAB除了做跨代引用,还可以用来清理RSET,当引用关系断开时,需要删除rset
Profiling收集程序执行状态
垃圾回收算法:复制和压缩算法分这几个阶段,标记-转移-重定向-清除-重置;清除算法与复制和压缩相似,只是无需转移和地址重定向
父类/接口
不同分支的classloader隔离
可中断的并发预处理concurrent abortable preclean:扫描新生代引用老年代的对象。
单线程收集仅适用单核
Scan Root Regions:根分区扫描,找到老年代根和survivor指向老年代的首个对象。下次GC前必须完成
SWT
Constant_Methodref_info
definePackage指定类所属的包,用于控制访问权限
InitingMark初始标记:MixGC借道YGC找老年代根,极快
单线程垃圾收集器--单线程最具吞吐量的回收算法适合单核cpu100M以内内存的系统(如果内存更大了,SWT无法忍受)
0:实例方法放this
G1ConcRefinementThreads多个线程
指向本region对象的引用数量可能很多,rset采用三种结构存储。被O引用就要记录rset非常占内存
M0:标记阶段new对象引用为当前颜色
M1
ZGC开启后new存放区域
Finalizable1位
注解处理器AbstractProcessor
copying
Warmup未触发过GC下当内存达到10%/20%/30%启动ZGC
扫描老年代引用年轻代的:cardpage或指定对象
无空间时触发
Callsite
GC root
年轻代每次GC收集会有大量98%对象死亡,适合复制算法,付出少量空间成本(s/2)就可以快速高效回收。系统稳定后,存活的2%,在下次或下下次YGC时会被清除,有了S区才让躲过本次GC的短活对象不会进入老年代
运行时BSM生成调用点BSM啥?百度
FGC
Rebuild Remembered Sets
直接访问对象属性
系统内存:需要几百M-1G
引用地址44位
终止MixedGC运行FGC
或者2卡精度扫描a+b
cpu1
n-m:局部变量
写入代码地址
距上次GC堆内存增长10%,或超过5分钟时等jvm自主决定-XX:+ZProactive
读堆内对象时触发读屏障
局部变量表:数组结构,0-N索引,值为引用变量:存放堆中对象的地址
老年代CMS(ConcurrentMarkSweep)-XX:+UseConMarkSweepGC设置该参数会自动使用ParNew不用再特别开启了,老年代占用空间达到分配空间的CMSInitiatingOccupancyFraction(默认92,一般70比较适合)时启动CMS。注意这个参数必须-XX:+UseCMSInitiatingOccupancyOnly(如果关闭这个参数,那么jvm会估算老年代耗尽的时间,提前CMS)才生效
invokeStatic静态
类装载子系统:类加载过程是加锁串行的
老年代可回收空间超过-XX:G1HeapWastePercent5%时启动MixedGC,否则就是普通的YGC
内存条
当太多了时借用户线程干活
4并发转移准备完成
#2->addr2
不需要
多标
e2
Concurrent Mark Cycle
并发转移准备Concurrent Prepare for Relocate:根据标记结果计算出参与回收的region,并放入Relocation Set
CMS线程
StackFrame栈帧(每运行一个方法就创建一个栈帧,执行完立即销毁栈帧)
Yong
getClass()
业务线程并发读取e1时
老年代中的对象存活几率很高能够回收的对象不多,且占用空间大,而且一旦内存不够,不像年轻代可以往老年代分配,不适合复制,只能选择清除或整理算法
老年代占比达到阈值
ZGC Cycles统计并行
数组长度4字节(不是数组没有)
Eden
动态链接:运行时在栈顶元素的类型中(栈顶元素.getClass)查找代码地址的方法。方法返回值,方法名,方法入参类型已经确定了,只是不明确到底是哪个Class的方法
invokeInterface接口
3写后屏障:当被其他O对象指向时触发注意是业务线程写入的
ZGC
验证字节数组,如cafebabe
并发重置Concurrent Reset:重置被标记的对象为白色
查找操作数栈栈顶元素的类型
Main.java
OOM
FGC回收全堆(SerialOld)
弹出栈中指定个数元素,执行jvm汇编指令,结果写到局部变量表或堆(注意:弹出后操作数栈就没了)
kernel
压缩:在触发指定次数FGC后,执行本阶段压缩算法,
但没有可供拷贝的空region时
F-Queue(已废弃)GC root定义了finalize方法的对象
字节码增强(类似aop,Instrument jvmti)
Constant_Utf8_info
字节码执行引擎
Preclean并发预清理:并行地做重新标记的事
card...
YGC root
操作数栈FILO
Remapped1位
JIT:逃逸分析与标量替换+同步锁消除:逃逸分析:查找不会逃出方法外的对象=局部变量+不会return到方法外的对象标量替换:将对象拆分成属性并分配到栈帧(局部变量表),不放到堆这样就可随栈帧销毁,减少YGC压力同步锁消除:锁不会并发是无意义的,消除同步逻辑
元空间不足Metadata GC Threshold
静态常量
当前/计算下一条jvm指令
晕了,都是旧数据
需要压缩?
解释器
32G以下内存未开启指针压缩 / 大于32G内存时KlassPointer占满8字节
Pause Cleanup:计算每一个region里面存活的对象,为后续分段回收O提供数据支撑,并把完全没有存活对象的Region直接放到空闲列表中,注意:本阶段不移动对象也不重定位
晋升失败:老年代不足以存晋升对象
并发标记漏标
SerialGC
ZGC必须使用64位引用,其中三位用于GC标记
并发失败concurrent mode failure:在并发标记、并发清理阶段,当老年代无法分配内存-老年代内存不够/空间碎片太多分配不下(在老年代新建/YGC后晋升对象,导致这个问题的原因是对象垃圾回收跟不上用户分配的速度)以及Metaspace耗尽时,会进入STW并切换到GFCSerialOld垃圾收集器
Tenured
b→b1c→c1e1→e2转发表
app:classpath指定的路径
入参值
Small Region=2M 存<256K
main对象
并发标记开始后新创建的对象/新生代YGC晋升的对象,这些对象会被标记黑色,下次GC时回收。
低STW的多线程并发垃圾收集器CMS:YGC->CMS->FGC适合多核cpu至少4核+4-8G内存,专注系统低停顿时间的垃圾收集器,将最耗时的标记和清理阶段与用户线程并行。且为了降低重新标记STW时间,提前做并发预处理和可中断的并发预处理,进一步降低重新标记STW。PN是为了配合CMS对PN做了微调的年轻代收集器。为了清理阶段与业务并行,CMS采用清除算法,这样无需对象转移和重定位,但清除算法引入了2个问题:NEW效率低和碎片问题导致有空间也无法分配,前者无法根治只能通过优化算法缓解,后者碎片问题借助FGC解决(整理算法),也就是说CMS必须依赖FGC,而G1和ZGC都是可以完全避免FGC的,默认在FGC前执行一次压缩,有些系统在闲时比如凌晨执行System.gc触发FGC以避免忙时触发FGC。CMS另一个问题是重新标记STW时长不确定,因为不确定并发预处理和可中断的并发预处理会留多少工作,有时会很多导致重新标记阶段STW就很长,可以考虑开启CMSScavengeBeforeRemark,重新标记前先来一发YGC,减少新生代数量减少标记量,但这是STW的..
线程栈(线程独占) GC Root:创建线程时同步创建,默认独占1M内存(-Xss指定)超出产生StackOverFlow异常,线程终止时回收分配给它的线程栈空间。线程上下文切换时保存线程栈即可
Pause Young (Concurrent Start):上一次YGC标记开启了MixedGC标记时,执行YGC
Tenured空间=(Eden+S0+S1)*2
动态链接
card2
重新标记Final Remark:处理漏标+标新生代指向老年代,-XX:ParallelGCThreads设置线程数,默认cpu核数
删除R0card2←R21card1
ByteBuffer.allocateDirect
MetaSpace(MethodArea)
Rset:其他O引用了我?
CMS root
MixedGc开启后,所有新创建的对象都是黑色的
用户线程/YGC线程
年轻代不够分配新对象Allocation Failure
OldGC后仍不够分配对象时启动FGC
年轻代写满时,计算回收年轻代时间,远小于指定STW时限时扩大E比例从G1NewSizePercent默认5%至G1MaxNewSizePercent默认60%,其中Eden-S0-S1按8:1:1分配;YGC后,发现老年代O占整堆比例达到阈值(-XX:InitiatingHeapOccupancyPercent默认45%)时准备启动MixedGC
拷贝后新Region0
未找到查父类
bitmap三色标记,不标在对象头上
可用的垃圾收集器:1垃圾回收器有两个重要的调优指标:吞吐量与停顿时间(STW),需要根据系统硬件和业务对吞吐量与停顿时间的取舍选择适合的垃圾收集器,适合的范围是95%-98%cpu时间用来做业务也就是吞吐量,2-5%cpu时间做垃圾回收 -吞吐量:cpu用于做业务的时间,吞吐量越高要求回收算法越高效,尽量少占用cpu时间片,这个对应的是SerialGC/ParallelGC/ParNew尽最快速度完成回收垃圾的工作。 -停顿时间(STW):系统停止做业务而专注做垃圾回收的时间,STW越低用户体验越好,更复杂的算法让STW尽可能短,但会消耗更多cpu去做并发算法同时降低了吞吐量,为此这些算法都会用空间换时间的策略降低cpu占用。***吞吐量可以通过增加机器数量解决,而STW只能用不同算法的垃圾收集器,在微服务时代我们更关注STW2长时间卡顿(STW时间长)+频繁卡顿(STW频繁)影响用户体验: -升级硬件-增加cpu个数+内存,可以将更多cpu给GC线程以缩短STW耗时,更大内存可以减少GC次数(但增大内存同时会延长垃圾回收时间,) -复杂的算法让用户线程与GC线程并行(CMS/ZGC并发标记+并发回收,G1并发标记+业务线程与STW回收线程交替运行)3垃圾回收器的发展方向主要为了更短的STW(吞吐量可以通过增加机器数量解决),和jvm可根据运行状况自动调整参数以减少人工调优的干预,例如:ZGC的目标是STW10毫秒+无需任何参数调优。4怎么选垃圾收集器 -极致吞吐量:少核cpu用SerialGc,多核用ParallelGC -极致STW:大内存ZGC,小内存不建议极致STW或者CMS(需重点关注重新标记阶段时长,以及FGC触发频率) -吞吐量和STW均衡:G1(G1可控制STW时限,但不能做到极致STW)5分代收集可以减少收集的区域,ZGC没有采用分代是因为算法太复杂了,另外全并行即便收集全堆也可以接受,对于cpu的占用可以通过提升cpu数量解决
b1
漏标:并发标记过程中引用关系发生变化,标记完时,a会被漏标,白色对象清除阶段会被删除。只要解决两条红线中的任一情况就能解决漏标问题
年轻代PNParNew-XX:+UseParNewGC,空间不够分配时触发AllocationFailure
针对2增量更新IncrementalUpdate(CMS使用):当黑色对象新增白色对象的引用关系时,重新扫描c。缺点:多扫描了b
不分代每次GC都要全堆扫描,耗时久,占用cpu降低吞吐量,更容易出现并发失败
-XX:+UseNUMA:ZGC能够感知与cpu物理位置最近的内存优先使用,减少对内存的并发争用,同时也可以访问其他内存
unused25bit
2标记完的c指向a
YGC时不用执行老年代的可达性扫描XXX
双刃剑:优点不用在年轻代来回复制,缺点可能引发FGC
cpu2
3修改引用地址
执行<init>:调用父类构造方法,为变量赋予初始指定值,执行构造方法的代码,返回对象地址
自定义类加载器(类加载器bootstrap/ext/appclassloader永远不会回收)被回收,无对象引用的CLASS
Region0(E/S/O/H)
当所有的用户线程不再产生GC Root时,才可以启动GC并进入STW,有两种情况:1savepoint:在不会改变引用关系的代码位置(一般是进入方法前、方法返回前、下次循环之前、抛出异常位置)插入testxxx的检查代码:检查指定的标志位被置位时,线程暂停。2saveregion指不会改变引用关系的状态如线程sleep、执行本地方法。
1.CMS new效率低(清除算法导致)2.有碎片CMS更易发生空间不足,压缩依赖FGC慢3.堆空间(年轻代与年老代)需人工调优4重新标记STW时长不确定高并发下可能雪崩5CMS依赖FGC必须执行为了压缩空间
大对象直接分配到老年代或G1 HRegion(-XX:PretenureSizeThreshold)
标记-复制:标记所有存活对象,移动到未使用的内存一端并原位置的对象forward记录移动后的位置,原内存区域整体回收,不会产生碎片,无需扫描全堆,但可用内存减少,适合存活对象少的回收过程(如果存活对象多,那拷贝对象和重定位会很耗资源);因移动了对象需要重定位:再一遍扫描所有存活对象,如果对象引用了移动前的地址,那用原位置的forward重新赋值引用地址;内存空间整体可用,指针碰撞(从头至尾一点点分配)分配对象空间快;内存连续,根据空间局部性原理命中率高,执行快。缺点:空一块内存+最怕70%以上存活对象需要复制那就废废了,使用这个算法必须保证年轻代达到并行峰值的内存
this
CMS foregroundSTW清除算法回收老年代
异步更新Rset
对象分代年龄4bit
1并发标记Concurrent Mark :根可达标记引用地址颜色为当前标记颜色2并发重定位:根可达遍历时遇到上次标记颜色M1时,对照找发表,修改引用地址。
Stack线程栈
需要
当前标记颜色=M0,上次=M1(每次GC切换M0↔M1)
Rset粗粒度位图(元素超多)key=region21value=1
加载指定URL的类重写findClass
基于固定时间间隔Timer
Serial GC单线程STW回收全堆
ParallelGC
Survivor
调用jvm.dll启动虚拟机
转移对象使用的新regionN(转移过程中,如果发现没有空闲页了,那么改为压缩算法region0)
FGC=YGC+OldGc+MetaspaceGc
TLAB:优先线程专属内存分配避免线程争用,默认为Eden的1%
Relocation Set:参与回收的Region集合
Concurrent Preclean+Pause Remark扫描
堆外内存(直接内存)
终止用户线程导致STW超长
card0
连续的空间存放对象(markword等于系统位数,64位系统workword占8字节):1无锁状态: 25b未使用 -31b对象hashcode-1b未使用-4b分代年龄-1b偏向锁0-2b加锁状态012偏向锁状态:54b获取偏向锁的线程ID-2b时间戳-1b未使用-4b分代年龄-1b偏向锁1-2b加锁状态013轻量锁状态:62b指向内部锁-2b加锁状态004重量锁状态:62b指向内部锁-2b加锁状态105GC标记: 62bXXX -2bParallelGC标记116指针压缩可以减少引用地址占用的内存空间,进而减少对象占用的空间,这样单个缓存行可以装更多数据,提高cpu命中率,减轻GC频次:-32位系统只能使用4字节引用地址-64位系统,在32G以下内存时,默认开启指针压缩,即以4字节存储地址,但35位才能表达32G内存,由于java对象8字节padding,所以可以右移3位以字节为单位寻址,用4字节最大寻址32G内存-32G以上时,KlassPointer和引用变量都使用8字节寻址,也就是强制关闭指针压缩
用户/YGC
G1
按入参定义时的类型匹配度优先查找代码地址
分段STW交替收集老年代(标记清除)
申请内存(优先TLAB,空间不够CAS指针碰撞/空闲列表争抢Eden)并为变量赋予类型默认值开启XX:+UseTLAB,XX:TLABSize
Dirty Card Queue元素极多
UMA
#1->addr1
并发转移Concurrent Relocate:将Relocation Set里的region中标记为当前标记颜色的对象移动到新的页。如果没有新页,那么使用压缩算法回收region
方法结束同步回收
静态变量
解决大内存下老年代STW长:并发标记+并发清理
s0from
不同类加载器加载的类是隔离的
YGC晋升失败/预估晋升失败
加锁标志位2bit
找到并读取.class文件为字节数组
标记-清除:标记所有存活对象,再一遍全堆扫描回收未被标记的对象,内存不连续,且有内存碎片,分配新创建对象时,需要搭配算法在空闲列表中查找存放的位置,因此创建对象是最慢的;清除算法极具优势的好处是无需移动对象,也就不用改引用地址了,可以解决GC线程与业务线程并发问题。与复制算法差不多,但创建对象慢就会导致业务执行慢了。
1依据停顿时间动态分配年轻代大小2大对象专区存放,用清除算法不复制3G1 Mixed采用分段复制算法,让STW收集过程与用户线程交替执行4自动调整参数,无需过多调优参数5老年代专属垃圾回收器,FGC全堆收集慢
否:扫描整个年轻代
card4
SATBMarkQueueSet
初始转移Pause Relocate Start:将GCroot指向的对象进行转移并修改引用指针颜色为remapped
CMS高版本已废弃
父类元信息Klass
当年轻代不够分配对象时启动YGC
Rset细粒度位图(元素多)key=region21value=bitmap 第0位=1
2查找转发表
年轻代ParallelGC-XX:+UseParallelGC
程序计数器(指令的地址)
并发失败:在并发标记浮动垃圾过多而导致无法分配内存时,终止Mixed GC运行FGC
R0card2←R21card1
5初始转移完成
XX
旧region0
ZGCPauses
关联执行对象
.class
基于分配速率的自适应算法Allocation Rate
FGC后空间仍不足以存放新分配或晋升的对象
是否为偏向锁1bit
invokeSpecial构造/私有方法/final修饰的方法
得到绑定关联对象的MethodHandle2
JVM通用重要参数:‐XX:MetaspaceSize和‐XX:MaxMetaspaceSize:元空间大小,默认21m,元空间不足时触发FGC,并从小至大提升比例,强力建议必须设置成相同的值(jdk8为-XX:PermSize和-XX:MaxPermSize)-XX:InitialCodeCacheSize和-XX:ReservedCodeCacheSiz:设置即时编译存放编译后代码的缓存空间大小,初始至最大,默认240M,code cache装满时,将不再新增编译执行的代码-XX:+DisableExplicitGC:开启后System.gc失效-Xloggc:输出GC日志到指定路径下的文件中,例如 -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log (%t表示jvm启动的时间,这样多次启动JVM时会区分开)-XX:+PrintGCCause(Parallel/CMS):GC日志中打印触发GC的原因-XX:+PrintGC(Parallel/CMS):GC日志中输出GC收集的概要信息-XX:+PrintGCDetails(Parallel/CMS):GC日志中输出详细的GC收集信息-XX:+PrintGCDateStamps(Parallel/CMS):GC日志中显示GC收集的输出时间,格式:年-月-日T时分秒.毫秒,必须开启-XX:+PrintGCDetails才生效-XX:+PrintGCTimeStamps(Parallel/CMS):与-XX:+PrintGCDateStamps相同,显示的时间是以jvm启动时间为基准的,便于查看耗时-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath:当JVM遇到OOM时,生成堆转储文件,即jmap-XX:+PrintFlagsInitial:GC日志中打印所有的默认参数-XX:+PrintFlagsFinal:GC日志中打印所有参数的最终值,如果某个默认值被新值覆盖,显示新值-XX:+PrintCommandLineFlags:GC日志中打印那些被新值覆盖的参数-XX:InitialCodeCacheSize:即时编译器存放编译后代码的缓存的初始大小-XX:MaxDirectMemorySize:设置直接内存最大空间,即Direct ByteBuffer可分配的最大空间,默认64MJVM通用辅助参数-Xss:指定线程堆栈空间大小,不够产生StackOverflowError异常,默认1M,大概可以放7000个方法,一般不用调整-XX:+PrintGCApplicationStoppedTime:打印GC时线程的停顿时间-XX:+PrintTenuringDistrution:输出YGC后survior区不同年龄对象占用的空间-XX:+PrintHeapAtGC:打印GC前后的堆信息,full(0)表示非FGC,full(数字)表示第几次FGC-XX:+PrintGCApplicationConcurrentTime :查看GC过程中用户线程并发时间-XX:+PrintGCApplicationStoppedTime:打印STW时间-XX:+PrintReferenceGC:打印回收了哪些类型的引用(SoftReference/WeakReference/PhantomReference/FinalReference),数量和耗时-XX:+UseGCLogFileRotation:开启GC日志文件分割。日志分割后会变成多份,还会循环覆盖,丢失信息,并且不方便使用分析工具-XX:NumberOfGCLogFiles:最多分割几个文件,超过之后从头开始循环写,例如-XX:NumberOfGCLogFiles=10-XX:GCLogFileSize:每个文件上限大小,超过就触发分割,例如-XX:GCLogFileSize=50MJVM通用谨慎参数-XX:+UseTLAB和-XX:TLABSize:开启TLAB并设置TLAB大小,用默认值即可。增加-XX:+PrintTLAB可以观测TLAB的情况ParallelGc重要参数:-XX:+UseParallelGC:使用ParallelGc和ParallelOldGc-Xms和-Xmx:堆大小,需至少给操作系统留下1G内存-Xmn:调整年轻代大小-XX:SurvivorRatio:设置年轻代eden和s0s1比例值,默认8:1:1-XX:+UseAdaptiveSizePolicy:自动调节新生代大小比例,包括E和S0S1比例,开启该参数就不用设置-Xmn和-XX:SurvivorRatio了-XX:MaxTenuringThreshold:进入老年代的分代年龄,默认15,设置小了容易发生短活对象(尤其秒杀场景)进入老年代引发CMS甚至FGC,设置大了长活对象多次在年轻大拷贝损耗性能-XX:PreTenureSizeThreshold:多大的对象会直接分配到老年代,默认1M,分配到年轻代会随着YGC多次拷贝而耗时严重,短活大对象分配到老年代浪费空间只能等FGC清理且加快FGC频率-XX:TargetSurvivorRatio:Survivor填充容量,默认50谨慎参数:-XX:MaxGCPauseMillis:设置最大STW时间。降低年轻代空间去适应这个限制,比如从1G降到500M,YGC虽然快了但也频繁了-XX:GCTimeRatio:0-100吞吐量设置垃圾收集时间占总时间的比率,与-XX:MaxGCPauseMillis只能二选一,也是通过调整堆空间大小实现的-XX:ParallelGCThreads:并行收集器的线程数,默认为cpu核数。一般使用全部的核心有助于降低STW,调高和调低都会变慢CMS重要参数:-XX:+UseConMarkSweepGC:使用CMS和ParNew-Xms和-Xmx:堆大小,需至少给操作系统留下1G内存-Xmn:调整年轻代大小-XX:SurvivorRatio:设置年轻代eden和s0s1比例值,默认8:1:1-XX:+UseCMSInitiatingOccupancyOnly:jvm会估算老年代耗尽的时间,提前CMS,设置该参数后将关闭这个功能,开启该参数是CMSInitiatingOccupancyFraction生效的前提-XX:CMSInitiatingOccupancyFraction:内存占比达到多少时触发CMS,默认92,设置低CMS频繁,设置高了可能导致FGC,一般观察GC日志做调整,至少70以上算合理。需要开启-XX:+UseCMSInitiatingOccupancyOnly才生效-XX:MaxTenuringThreshold:进入老年代的分代年龄,默认6,设置小了容易发生短活对象(尤其秒杀场景)进入老年代引发CMS甚至FGC,设置大了长活对象多次在年轻大拷贝损耗性能-XX:+UseAdaptiveSizePolicy:开启自动调整-XX:SurvivorRatio和-XX:MaxTenuringThreshold-XX:+CMSScavengeBeforeRemark:在可中断并发预处理阶段,需要扫描整个新生代标注老年代,如果新生代很大那么扫描会很耗时,而开启这个参数后会先来一发YGC,只需扫描survivor引用老年代,减少引用数量,可以缩短下一阶段重新标记STW时间,还能降低并发清理时出现YGC的概率,避免连续STW的情况。观察到CMS时Eden都很大可以开启该参数-XX:CMSScheduleRemarkEdenSizeThreshold:当 eden 区空间超过该值时才执行可中断的并发预处理,默认为 2M-XX:CMSScheduleRemarkEdenPenetration :当 eden 空间使用率大于该值时,中断可中断的并发预处理阶段,进入重新标记阶段,默认为 50%-XX:CMSMaxAbortablePrecleanTime :可中断的并发预处理阶段最长的执行时间,达到这个时限后停止,进入重新标记阶段,默认为 5s,与CMSScheduleRemarkEdenPenetration同时生效-XX:+UseCMSCompactAtFullCollection:开启FGC后执行压缩-XX:CMSFullGCsBeforeCompaction:执行指定次数的FGC后执行对老年代的压缩算法,必须开启-XX:+UseCMSCompactAtFullCollection才生效,默认为0也就是说每次FGC前都对老年代进行压缩,可以根据CMS的频率,调整该值3-5-XX:+CMSClassUnloadingEnabled开启CMS对永久代进行垃圾回收,即卸载类(回收Class)-XX:PreTenureSizeThreshold:多大的对象会直接分配到老年代,分配到年轻代会随着YGC多次拷贝而耗时严重,短活大对象分配到老年代浪费空间且会加快CMS频率,如果CMS频率高可以检查是否有短活对象分配到了老年代-XX:TargetSurvivorRatio:Survivor填充容量,默认50谨慎参数:-XX:MaxGCPauseMillis:设置最大STW时间。降低年轻代空间去适应这个限制,比如从1G降到500M,YGC虽然快了但也频繁了-XX:ParallelGCThreads:并行收集器的线程数,默认为cpu核数。一般使用全部的核心有助于降低STW,调高和调低都会变慢-XX:+ExplicitGCInvokesConcurrent:无论什么时候System.gc都只执行CMS而不是FGC,有什么用呢?不启动FGC,让CMS帮助回收DirectByteBuffer。但有的时候,我们调用System.gc就是为了执行FGC好执行老年代的压缩算法-XX:+CMSIncrementalMode:开启增量模式,cpu核数少或者业务极其繁忙cpu不够用时,开启该参数时,CMS过程会经常暂停,将cpu交给业务线程,代价是这会拉长回收周期。拉长有什么不好呢?CMS容易出现并发失败转成FGCG1重要参数-XX:+UseG1GC:使用G1-Xms和-Xmx:设置堆大小,需至少给操作系统留下1G内存,G1会按这个大小划分给2048个region,例如-Xms128M。以前要求设置相同这样可以减少扩缩容带来的性能损耗,但云原生时代扩缩容变得有意义,可以将不必要的内存还给操作系统可以降低成本,并且将资源留给更需要的应用-XX:G1HeapRegionSize:指定单个分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区,与-Xms和-Xmx只能二选一-XX:MaxGCPauseMillis:设置垃圾回收STW最大时间,默认200ms,JVM会尽力在这个时间范围内,但不保证。该参数不能设置得太小,否则根本收集不完新生代,只能降低新生代空间,导致YGC频繁发生。此外,MixedGC分段收集也受这个参数限制-XX:GCTimeRatio:设置垃圾收集时间占总时间的比率,即吞吐量,与-XX:MaxGCPauseMillis只能二选一。-XX:ParallelGCThreads:STW期间,GC线程数,默认cpu核数,一般不用调整-XX:ConcGCThreads:并发标记阶段,GC线程数,复杂的算法大概1/4CPU核心数-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent:年轻代占整个堆的初始比例(5%)和最大比例(60%),G1会根据STW时限要求或吞吐量自动调整,当然可以预先设置好例如调高初始比例-XX:SurvivorRatio:设置年轻代eden和s0s1比例值,默认8:1:1-XX:MaxTenuringThreshold:进入老年代的分代年龄,默认15,设置小了容易发生短活对象(尤其秒杀场景)进入老年代引发CMS甚至FGC,设置大了长活对象多次在年轻大拷贝损耗性能-XX:InitiatingHeapOccupancyPercent:老年代占用整个堆空间的比例达到阈值45%时,触发MixedGC(回收E/S/部分O/H)。设置小了,MixedGC频繁;设置大了,MixedGC时可能找不到空闲region引发FGC-XX:G1HeapWastePercent:触发MixedGC在重新标记后,统计cset中可被回收的垃圾占整堆的比例值(默认5%),如果有超过这个比例的垃圾可以被回收,那么才会启动多段MixedGC,否则退出,等待触发G1 YGC再清理垃圾,提高这个比例可以降低MixedGC频率-XX:G1ReservePercent:指定G1为YGC晋升保留的空间,默认10%。这个空间为存储年轻代晋升的对象而保留,也可以用作GC复制的region,降低FGC概率。调高此阈值意味着降低了老年代的实际可用空间-XX:G1MixedGCLiveThresholdPercent:old region中的存活对象低于这个值时才会有可能参与本次回收(进入cset),默认85%,如果超过这个值,那么存活对象多移动对象就会非常耗时,缺点就是浪费了空间-XX:G1OldCSetRegionThresholdPercent:在每一次分段mixed gc中,参与回收的old regions大小总和占堆比例上限,默认值10%-XX:G1MixedGCCountTarget:MixedGc可以分的段数,默认8,也就是MixedGC与业务线程交替执行最多8次,本次MixedGC完成-XX:+AlwaysPreTouch:启动时,强制操作系统真正分配所需的空间。jvm指定堆大小,但操作系统并没有真正分配,而是在用到时才分配,这会导致STW延迟,且内存碎片多的问题。缺点是启动变慢-Xlog:gc*:${LOGPATH}test-gc-%t.log:G1将PrintGCDetails和PrintGCDateStamps等去掉了,合并到这一个参数-XX:TargetSurvivorRatio:Survivor填充容量,默认50谨慎参数:-XX:GCDrainStackTargetSize:并发标记阶段,可以递归的最大层数,默认64。例如,标记对象A时,找到引入了B,扫描B发现了C,扫描C发现了。。。,一共可以套64层-XX:G1ConcRefinementThreads:Rebuild Remembered Sets阶段使用的线程数,默认8禁用参数:-XX:NewRatio、-Xmn:G1根据停顿时间调整年轻代大小,如果设置了这个参数,G1动态调整空间分配将会失效ZGC重要参数-XX:+UseZGC:使用ZGC-XX:MinHeapSize:最小堆内存,=-Xms -XX:InitialHeapSize:初始堆内存-XX:MaxHeapSize:最大堆内存,=-Xmx-XX:ParallelGCThreads:STW期间,GC线程数,默认cpu核数,一般不用调整-XX:ConcGCThreads:并发阶段,GC线程数,复杂的算法大概1/4CPU核心数。调大这个数可加快回收速度-XX:ZAllocationSpikeTolerance:按对象分配速率估算启动ZGC时机,默认2,调大该参数可以尽早触发ZGC,一般有需要调为5即可-XX:ZCollectionInterva:按时间间隔触发ZGC,该参数触发ZGC发生的最小时间间隔,默认值0,单位秒
Main
M
Marked01位
CMS过程中,ParNew晋升失败promotion failed
惰性重定位:转移阶段只移动对象不重定向,当业务线程访问时才发起重定位流程
MethodHandle是运行效率远高于反射Method一种执行模式(无需权限检查,可以jit优化)
最终(重新)标记Pause Mark End:处理并发标记过程中漏标的对象。本步骤完成后,清空转发表
各代垃圾收集器的改进
并发失败FGCconcurrent mode failure
Medium Region=32M 存<4M
JIT:invokeVirtual方法内联:节省创建堆栈帧的时间,如get/set方法
Main.class
markword(无锁状态)
栈帧:invokeXX触发创建
并发清理Concurrent Sweep:回收未被标记的对象,这里采用标记-清除算法(无需移动对象+修改引用地址)会产生大量碎片。占用10%时间
-XX:ConcGcThreads:指定与业务线程并行时GC的工作线程数量-XX:ParallelGCThreads:指定STW时GC工作的线程数量
将.class中的#1 #2 符号引用转换为直接引用(这些符号存放的实际内存地址)
分代收集理论(分治):年轻代变化快回收率高,老年代变化慢回收率低(ZGC不再分代)
新增R0card2←R21card0
当老年代达到触发阈值触发OldGc
CSET:选中参与回收的regionYGC:E/S MixedGC:E/S/H/部分O FGC:E/S/H/O
方法出口
MethodType=MethodType.methodType(返回值类型,入参类型)除方法名和所属Class的方法签名(Method全含Class+返回值类型+方法名+入参类型)
Rebuild Remembered Sets:将写前、写后屏障引用关系变更更新rset。这个阶段时长不确定可能会很长,所以是并行的
当发现引用地址颜色为上次标记M1时,对照转发表修改指针,并调整颜色为M0
年轻代SerialGC-XX:+UseSerialGC
全局cardtable数组(老年代) 索引=cardPage起始位置<<9元素=字节而不是一个位,原因是效率比位更高
aload0
X
1写前屏障:当处于并发标记阶段+删除灰色指向白色引用时,写入
#4->addr4
Constant_Fieldref_info
2
.class里定义的常量池
并行收集老年代(标记清除)
Rset稀疏表(默认)key=region21value=[0](card0引用了我)
已标记完存活的对象被清除(如引用置null、线程终止),下次GC再清理即可
执行<clinit>:对类的静态变量赋予指定值,执行静态代码块Class.forName入参初始化为false时不执行这步
s0
堆外内存(不受GC回收)
得到MethodHandle1
1未标记b置空
初始标记Pause Mark Start:找全堆根,极快
静态链接:编译器生成.class时已经写明代码地址
code cache装满时,将不再新增编译执行的代码-XX:InitialCodeCacheSize设置初始大小-XX:ReservedCodeCacheSize设置最大大小,默认240M
Region21(O)
1初始标记完成
classloader全盘负责懒加载 GC Root
堆:‐Xms2048M ‐Xmx2048M设置堆大小 ‐Xmn1024M设置Eden大小 -XXSurvivorRatio设定Eden/s0/s1比例,默认8:1:1,但JVM会自动调整(-XX:+UseAdaptiveSizePolicy默认开启Eden/s区比例自动调整)
Padding父类属性(存在父类Object除外)为当前这个父类(不含子类)的属性之和(蓝)补足8字节的整数倍
1对象精度扫描a
MethodArea:
1.使用大内存时,STW总时长过长,尤其YGC2.STW不能极限短(对象转移与业务线程不能并发),极限场景不适用如手机系统3.读屏障替代G1前写后屏障,性能更高(无伪共享问题)5.MixedGC转移对象没有空region时会转成FGC,而ZGC先采用压缩算法完成部分region回收后再复制6不分代没有跨代引用无需rset占用空间和维护损耗7更少调优参数8YGC会打断MixedGC,极端情况不会执行,只能等待极慢的FGC清理老年代
类元信息结构
并发预处理Concurrent Preclean:并行处理漏标,以减少重新标记的工作量
Pause Full:并发失败,触发并行FGCAttempting full compactionSTW复制回收全堆+回收SoftReference
其他region..
2修改指针
s1to
浮动垃圾因全堆标记较多
G1线程
Remapped
并发失败Allocation Stall
HumougsG1将大对象区从老年代独立
CMS过程发生YGC?
c1
GC后拷贝到新region
年轻代够分配新对象
并行收集整堆不分代(标记复制)
x
code cache(c++/cpu指令/汇编)
在G1里每个card只放一个对象,如果一个card放不下,那么多个card存。这里非常浪费内存。好处也很明显,回收内存效率极高,如集装箱码头
3最终标记完成后
直接内存DirectByteBuffer
0 条评论
下一页