999_JVM原理
2024-05-23 21:30:45 0 举报
JVM原理深度剖析
作者其他创作
大纲/内容
survivor 210%
老年代空间是否> 历次minorGC后进入老年代对象大小平均值
minor GC ...
发生Handle Promotion Failure
新生对象Aage 2
使用中的局部变量
存活对象
ParNew+CMS垃圾回收过程原理
将这次回收的所有region中存活的对象复制到空闲的region中
Eden 80% = 1.6G
指向堆地址
G1 将jvm内存分为同样大小的n个region(Eden,Survivor,Old动态分配)堆大小/2048 = 每个region大小,通过-XX:G1HeapRegionSize指定
jstat统计发现,并不是频繁的有对象进入老年代而导致的full GC,就应该考虑是其他方面的问题,如:系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,必然频繁触发Full GC
解析符号引用替换为直接引用
全清空
b1
JVM整体运行原理及内存区域
2 并发标记,跟踪GC Roots直接或间接引用的对象
系统间歇性提供服务
b(n)
region2m(Eden)
触发mixed混合回收
stop the world
通过大量反射动态生成类,造成类巨多,触发Full GC后仍然无法腾出空间存放新的类信息
垃圾回收器(CMS)
线程
用Jetty的项目报nio handle failed java.lang.OutOfMemoryError: Direct buffer memoryat org.eclipse.jetty.io.nio.xxxx
Full GC的解决思路
类静态变量
survivor 110%
运行结束load() 出栈mian() 出栈
解法:合理增加新生代Eden和Survivor大小,避免过多的对象进入old区
STW 200ms
老年代空间是否> Eden所有对象?
动态年龄和年龄够大进入老年代
class User{ static A a = new A() mian(){ C c = new C() load() } load(){ while(true){ B b = new B() } }}
region2m(Old)
N
准备1 给类分配内存空间放到方法区2 初始化类变量也就是静态变量,比如int初始值0等
a 经过15次GC发现依然有静态变量引用,所以将其移到老年代
堆内存引发OOM
Y 直接移到老年代
设置,每次回收stop the world最多停顿200ms-XX:MaxGCPauseMills = 200ms
永久区300m方法栈controller service组件
region2m(S2)
说明:初始配置jvm内存一共4GB,新生代1.5G,老年代1.5G,线程栈1m,永久代300m,开启minorGC后的老年代空间判断。-Xms3072m -Xmx3072m -Xmn1536m -Xss1m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:HandlePromotionFailureHandlePromotionFailure jdk1.6之后就废弃,默认就是打开的。
JVM虚拟机
垃圾回收可达性分析查找GC Roots
系统
动态年龄判断机制
系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,稍微有一点对象进入老年代就引发Full GC,导致频繁触发Full GC
新生对象Fage0size = 500M
新生对象Bage 1
--------------------------原因猜测:--------------------------1、direct buffer memory指的就是jvm外的系统内存,内存溢出说明无法再分配堆外内存导致。2、只有无限创建nio的DirectByteBuffer才可能导致这个问题--------------------------工具:--------------------------jstat:发现新生代,老年代内存设置极不合理,survivor区非常小,而老年代又很大。导致很多对象在young gc后直接进入了老年代。--------------------------分析:--------------------------1、系统在高并发下,创建大量的directByteBuffer对象,但是处理时间较长,young gc后survivor区太小,导致这些对象进入了老年代。2、由于老年代比较大,full gc很少,所以大量的directByteBuffer滞留在old区并且关联着堆外内存。3、nio在内次申请direct memory的时候会system.gc()提醒jvm进行内存释放,本身是可用避免堆外内存溢出的情况,但是jvm启动时的参数禁用了显示system.gc,所以nio的内存清理功能失效了。--------------------------解决:--------------------------1、合理设置Eden、survivor和old区大小,让directByteBuffer在survivor区就清理掉,别进入old区2、允许system.gc显示操作。(这个视项目场景而定,通常是要禁用的)
region否足够?
垃圾回收线程
执行回收,间歇性回收过程,保证STW在可控范围(分8次回收 -XX:G1MixedGCCountTarget =8)(堆空闲5%停止mixedGC:-XX:G1HeapWastePercent =5%)
3、最终标记,STW,根据并发标记阶段对象的修改,最终标记存活或垃圾
新生对象Dage 1
对象 A
OOM
G1 垃圾回收下的运行模型
Y
新生对象Aage0
执行回收
找到了
准备阶段,就将类和方法加载到方法区,也就是永久代
尝试minorGC
第一次minorGC
此判断是为了防止Eden所有对象都存活下来,并且大于survivor区,直接移到老年代,而老年代空间又不够的情况
加载原则: 用到的时候才加载还要按照双亲委派机制
c
老年代 1.5G
线程栈内存300m每线程栈1m约300个线程
计算老年代每个region大小,数量
双亲委派机制
编译后的HelloWorld.class字节码文件
新生对象Bage 2
超大对象
Metaspace引发OOM
在高并发请求,处理时间较长的业务中,对象存活时间太长,进入老年代后无法回收掉
回收之前调用finalize()
web项目打包上线后,tomcat报OOM,jvm heap OutOfMemory
1、核心对象35个DTO 约1mb2、每秒处理80个行为
region2m(S1)
CPU负载过高,抢占不到CPU资源
解法:-XX:+DisableExplicitGC
系统继续运行
老年代
GC Roots和对象的引用关系
在高并发下,directByteBuffer对象一直引用着堆外内存不释放
每次GC对象的年龄+1经过默认15次GC如果年轻代对象年龄超过15,就移到老年代
新生代默认5%region,通过-XX:G1NewSizePercent指定最多不超过60%,通过-XX:G1MaxNewSizePercent指定进行垃圾回收后,Eden可能还会减少
Y 考虑回收
minorGC后存活对象大于survivor空间,全部移到老年代
方法区
触发Full GC
系统运行
--------------------------原因猜测:--------------------------1、自己controller处理请求太慢,导致tomcat的work线程一直持有请求对象无法释放。2、controller本身处理很快,但是依赖了远程服务,而且timeout设置过大,导致远程服务异常后,本地controller等待很长时间3、tomcat的max-http-header-size设置过大,使得tomcat为一次请求开辟较大的内存数组--------------------------工具:--------------------------MAT分析,找出了占据内存的byte[]数组--------------------------分析:--------------------------byte[]并不是程序创建的,而是tomcat设置max-http-header-size导致一次请求就开辟10M内存,然后controller依赖远程服务宕机,超时时间设置过大,这样一次请求花费4~6s的时间,在这个时间段,如果每秒有100个请求,那么4秒就会占满4G的内存空间,进而导致最终的堆OOM。--------------------------解决:--------------------------请求远程超时时间缩小到3stomcat请求大小设置为3m
-XX:pretenureSizeThreshold大于此参数的对象直接进老年代减少在survivor之间来回复制大对象
Old Region存活率小于85%?
内存设计不合理,导致对象频繁进入老年代,引发Full GC
1 初始标记,STW,仅标记GC Roots直接引用对象,如局部变量或静态变量
jvm虚拟机栈他的作用就是在调用方法时,创建栈帧,然后将方法及局部变量压入栈中
第二次minorGC
垃圾回收后台线程按照垃圾回收机制处理堆中对象
ParNew + CMS 系统运行模型及垃圾回收分析
Eden 80% = 1.2G
一层层查找
main方法栈帧A a = new A()局部变量 a
b16
永久代里类太多,触发Full GC
JVM内存区域
80mb
计算【回收价值】用最短的stw回收最多的region
大于单个region的50%就是大对象,大对象有可能跨多个region
年轻代-Xmn
年轻代
解法:合理监控和调试,增大永久代(通常一般系统设为256M)
a.load()栈帧
是否配置--XX:HandlePromotionFailure
未知冗余400m,可能被线程栈占用
堆外内存引发OOM
Eden 80%
永久代perm / meta(即方法区)User.class-XX:PermSize-XX:MaxPermSize
S2 150m
栈
S2 200m
a
每次YGC后,Old区都有几十兆增加,这时候就要看是否survivor是否太小,导致对象动态年龄判断直接进入old区
应用程序类加载器
切换单线程标记清理整理
新生对象Cage0
找不到
取消引用
不停创建
停止系统
region2m
不断调用某个方法,比如递归,无限将每个栈帧中的局部变量压入线程栈
老年代空
扩展类加载器
minor GC 3
机器:2核8G系统:4Gjvm:4G
分析1、对象基本上都止步于survivor区2、一些标记的@controller @service等组件,尽快到老年代,不占用survivor空间3、大于1m的对象,比如缓存List等,直接放入老年代4、缩减老年代空间,提高内存利用率5、指定垃圾回收期,parNew 和 CMS6、Eden高频清理的垃圾对象进入老年代的几率很小,所以老年代占用超过92%触发Full GC,留8%给并发清理时冗余。7、由于Full GC触发次数非常少,所以每次Full GC后,直接进行内存碎片整理即可。
4、混合回收,STW
老年代是否有空间存放存活的对象?
堆
栈帧(方法局部变量入栈)
用户行为系统
G1 Mixed混合垃圾回收(基于复制算法)
自定义类加载器
新生对象在内存中的流转
字节码执行引擎
解法:jmap导出dumpMAT分析,老年代对象是什么
永久代perm / meta(即方法区)User.class
虚引用得知对象被GC的时机常用来释放堆外内存,如DirectByteBuffer的释放
Old Region占据堆内存45%(-XX:InitialtingHeapOccupancyPercent)
b2
初始化1 变量赋值2 静态代码块初始化初始化时机:1 new实例化的类对象2 mian方法主类3 子类初始化的时候先初始化父类
说明:优化配置调整新生代2G,老年代1G,新生代Eden50%-Xms3072m -Xmx3072m -Xmn2048m -Xss1m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreShold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforCompaction=0
系统承载高并发请求,或者处理数据量过大,导致Young GC很频繁,而且每次Young GC过后存活对象太多,内存分配不合理,Survivor区域过小,导致对象频繁进入老年代,频繁触发Full GC。
回收各个区Region
新生代 1.5G
弱引用WeakReference垃圾回收时就被回收
新生对象Eage0
导致fullGC排查思路
age > 15
空间分配担保规则每次minorGC前都进行规则校验
(-XX:G1MixedGCLiveThresholdPercent)
存活对象小于老年代
minor GC 2
新生对象Dage0
GC Roots(其实就是最顶层的引用变量)
Survivor和Eden的比例通过-XX:SurvivorRatio = 8 指定
垃圾回收算法(标记整理,减少内存碎片)1、初始标记,stop the world2、并发标记,同时系统运行,深度跟踪GC Roots,耗时但是不影响系统运行,但是占用CPU(启动线程数=cpu核数+3/4)3、重新标记,标记遗漏的垃圾对象,stop the world4、并发清理,一般到92%时进行清理,同时系统运行,留8%冗余留给新移入的对象(浮动垃圾),避免Conccurent Mode Failure,此时触发serial GC,stop the world
寄存器记录指令位置
空闲region是否足够?
放弃回收此region
200mb
老年代 1G
OOM定位分析方法
启动类加载器
minor GC 15
优化
S1 150m
验证验证字节码是否符合规范
分析1、minorGC会在运行15秒后,Eden区内存达到1.2G2、minorGC后,正常会有80m左右的对象移到S1区3、80m的对象需要移到S1,由于survivor区较小,有可能超过survivor区可用大小的50%,可能引发动态年龄机制,让过多的对象进入老年代优化:年轻代1、增加survivor区大小,并且考虑minorGC后移入对象大小尽量不能大于survivor的50%1、缩减老年代大小2、根据系统特性,没有必要让对象躲过15次垃圾回收后才进入老年代老年代1、当对象达到92%的空间占用触发Full GC2、Full GC后就整理内存碎片
JVM通用模板-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oomCMSParallelInitialMarkEnabled:初始标记STW的时候,采用并发标记,加快速度CMSScavengeBeforeRemark:重新标记STW的时候,先YGC一下清理年轻代垃圾,减少对象,从而在重新标记阶段可以少扫描一些对象,提升速度。DisableExplicitGC : 禁止程序调用system.gc
线程栈引发OOM
minor GC 1
程序BUG
可达性分析算法
直接进行minorGC
新生对象Aage 1
新生对象Bage0
Metaspace(永久代)因为加载类过多触发Full GC
b1 ~ b15 占满了年轻代,创建b16的时候发现年轻代满了,就进行minorGC,b1~15都没有栈引用,所以全部清理掉了
Y 执行回收
频繁Full GC导致Stop the world
存活对象小于survivor
HelloWorld.class静态变量
GC
强引用不会被回收
软引用SoftReference内存可能溢出时被回收
线程虚拟机栈-Xss
系统创建的对象太多无法释放
内存泄漏,内存中驻留大量对象,一直塞满老年代,稍微有一点对象进入老年代就引发Full GC
移到survivor区
触发FullGC清理所有空间
内存占用过大,被os杀掉,而监控服务将其再次拉起来
S1 200m
存放类信息
内存泄漏,造成内存无法回收
新生代 2G
垃圾回收器(ParNew)
JVM内存区域-Xms -Xmx
寄存器
垃圾回收算法(复制)
系统创建了太多的DirectByteBuffer
扫描堆A对象无人引用认为是垃圾被回收
age1+age2+age3的总大小大于某个survivor的50%,那么就将age4以上的存活对象直接移到老年代
当创建对象时发现年轻代无法装载下这个对象时,出发minor GC
加载
0 条评论
下一页