引发Full GC的情况
- 老年代空间不足:老年代数据来源于新生代大对象的分配担保以及长期存活的大GC年龄的对象。为了避免上述两种数据来源,调优时候尽量在minor GC时回收。以及尽量避免创建大对象或者增大新生代大小,尤其是s1、s2大小。
如果Full GC失败抛出:java.lang.OutOfMemoryError:Java heap space
- “永久代”(JDK1.7之前)空间不足,加载过多的类元数据信息、静态变量、常量(运行时常量池)。所以应该增大方法区空间,-XX:MaxPermSize。
如果Full GC失败抛出:java.lang.OutOfMemoryError:PermGen space
- 当出触发YGC时候,JVM检查老年代最大连续空间是否大于新生代所有的总和,如果不大于的话,这次YGC是有风险的,在看是否开启分配担保,
- 如果分配担保设置为不允许冒险(HandlePromotionFailure)则触发Full GC
- 如果分配担保设置为允许冒险,但历次平均晋升到老年代的大小大于当前老年代最大连续空间,也触发Full GC;
- System.gc()方法:建议触发Full GC,但不一定;
针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
- Partial GC:并不收集整个GC堆的模式
- Young GC:只收集young gen的GC
- Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
- Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式
- Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。
作者:RednaxelaFX
链接:https://www.zhihu.com/question/41922036/answer/93079526
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如何排查频繁Full GC【4】
- 首先知道Full GC的产生原因,如频繁调用System.gc(),代码自检即可或者GC日志中也有描述,排查较为简单;
- 其次Full GC的产生原因也可能是方法区(永久代or元空间)或者老年代空间不足导致的,使用
jstat -gcutil
看下各个代的使用情况 - 使用
jmap -heap
查看各个代的配置信息是否过小
- 如果觉得配置正常,再使用
jmap -histo:live | head 20
统计堆中对象信息,自检代码相关的占用大的数据类型,是否存在内存泄露
- 如果还需要进一步确认,
jmap -dump
导出内存文件,使用MAT、JProfile等工具分析,包括查看GC ROOT是否存在内存泄露的问题
JVM相关调优
Full GC是GC整个堆,速度很慢。如G1的mix GC来不及回收的话,切换到Full GC的话,使用的是Serial Old GC,单线程停顿时间长,特别影响程序和性能。为了避免Full GC,我们可以从避免产生Full GC的原因开始:
- 如System.gc()这个是代码层的,不在调优范围内;
- 方法区或者老年代过小,可以修改相关参数-XX:xmx最大堆内存(堆内存=新生代 + 老年代), -X:xmn(也可以使用-XX:MaxNewSize)年轻代大小,-XX:SuvivorRadio:设置Eden比例,元空间Max默认机器内存无需设置,-XX:MaxPermSize老年代大小
- 当进行YGC时候,如果新生代当前对象大于老年代的空闲连续空间,且未开启冒险分配担保(HandlePromotionFailure),则会进行Full GC,如果能保证每次晋升的对象都比较少,此设置应该开启
- G1中,如果负载较低的话,将-XX:InitiatingHeapOccupancyPercent调低一点,及早触发全局标记,在配合-XX:G1HeapWasterPercent提前触发进行MixedGC,防止Full GC。因为Mixed GC会回收老年代。
- 替换垃圾收集器,-XX:UseG1GC
- G1的-XX:MaxGcPauseMillis不宜设置过小,可能会导致MixedGC回收来不及而产生Full GC
如何排查机器负载高
方法1
- 用ps -ef | grep tomcat-查出tomcat运行的进程id(17323)
- 用top -H -p pid 查询tomcat进程下所有线程的运行情况,线程(17734)
- 导出java进程dump文件到某目录,jstack -l 17323 > /root/17323.stack
- 打印CPU消耗较多的线程id(17734),jstack打印的线程堆栈中线程id是十六进制的,所以需要将十进制的线程id转换为十六进制(17734 -> 4546)
- 在导出的17323.stack文件中搜索打印出来的0x4546,就能找到引起cpu过高的线程
https://blog.csdn.net/u010248330/article/details/80080605
https://time.geekbang.org/column/article/192234
方法2:
https://www.jianshu.com/p/6afda390dcd5
内存泄露和内存溢出
- 内存映像工具,-XX:+headpDumpOnOutOfMemoryError:让虚拟机在内存溢出的时候,导出当前内存快照
- Dump出快照
jmap -dump:live,format=b,file=heap.bin <pid>
- live表示只dump活跃对象,format=b表示二进制格式,file表示保存文件的路径,pid表示进程号。
- 分析内存泄露和内存溢出
- 内存泄漏:查看泄露对象到GCROOT的引用链,查找关联路径
- 内存溢出:检查堆参数(-xms -xmx),代码检查对象的生命周期是否过长
有哪些打破了双亲委托机制的案例【1】
JDBC:通过SPI机制(Service Provider Interface,向下委托),用ServiceLoader.load
去加载:在BootstarpClassLoader加载DriverManager的时候,通过SPI向下委托(通过线程上下文ContextClassLoader获取AppClassLoader)AppClassLoader加载mysql驱动下的包;
什么情况会造成元空间溢出?
元空间主要存储的是类的元数据信息、运行时常量池(类元数据信息一部分,字面量和符号引用/直接引用)、JIT编译后的代码缓存。主要从这三方面入手,最常见的还是加载的类过多导致溢出。
什么时候会造成堆外内存溢出?
使用了Unsafe类申请内存,或者使用了JNI对内存进行操作。这部分内存是不受JVM控制的,不加限制的使用,容易发生内存溢出。
有什么堆外内存的排查思路?【2】
- 使用top命令查看RES(实际存储,VIRT:虚拟存储,即总申请量),如果RES值远大于-XX:Xmx说明堆外内存占用大。
- 通过jmap -dump分析内存堆中,找到DirectByteBuffer的引用和大小
- 使用gdb将物理内存dump下来进行分析
- 使用perf查看申请最多内存的native函数
垃圾回收器的哪几个重要参数?
G1:
-XX:MaxGCPauseMillis:目标停顿时间
-XX:G1HeapRegionSize:设置region单个大小
-XX:G1NewSizePercent:新生代最小值,默认值5%
-XX:G1MaxNewSizePercent:新生代最大值,默认值60%
-XX:InitiatingHeapOccupancyPercent:默认45%,初始堆并发比例,超过堆xx比例后触发全局并发标记,负载小的情况下可以适当调低,有利于提前gc,防止full gc
-XX:G1HeapWastePercent:默认5%,全局并发标记结束后统计出可回收的垃圾占比heap超过xx比例则触发后续的MixGC。和InitiatingHeapOccupancyPercent一起使用可以提前回收老年代。
为什么G1用SATB?CMS用增量更新?
SATB相对增量更新效率会更高(但也会造成更多浮动垃圾),因为SATB不需要像增量更新一样在重新标记阶段对增量引用进行深度扫描,只是简单标记,等下一轮GC再深度扫描。
原因是G1很多对象位于不同region,而cms都在老年代,深度扫描来说G1代价比CMS高。
G1和CMS区别
CMS基于标记-清除算法实现,G1整体是基于标记-整理算法,所以CMS会产生空间碎片。
并发标记过程中,CMS采用三色标记+增量更新的方法而G1采用三色标记+原始快照;
CMS是一个低停顿的垃圾收集器,而G1是时间可预测时间停顿的(-XX:MaxGCPauseMillis)
G1是空间布局是基于均分的region实现的,只是region扮演eden、survivor、old等区域,另外还有一个houmongous角色
G1特有的mixed GC回收young区和部分old区
GC ROOT
包括活动线程相关引用,比如栈桢里的本地变量表引用
JNI引用
类的静态变量引用
运行时常量池引用(Class类型或String)
为什么JDK8移出了永久代引入元空间?【3】
由于 PermGen 内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM。
移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。
元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。(和后面提到的直接内存一样,都是使用本地内存)
-XX:MetaspaceSize 初始化大小
-XX:MaxMetaSpaceSize 分配的最大值,超过此值会触发Full GC,默认没有限制等于系统内存大小
面试大纲
《不看后悔》超赞!来一份常见 JVM 面试题+“答案”!:https://mp.weixin.qq.com/s?src=11×tamp=1632882811&ver=3343&signature=EyRzO9nta90zTB0LvKymW8ViuTM3PbYfhD6HU2gwZGizre-6jdhyPczIA2v3zRLEU7wQoQG26p8CeYUlKiIk5xERqkypQOxj-OaJZQfslmBBtg1Bv9OjCAWKRW6n9S&new=1
一篇与面试官和蔼交流的深入了解JVM:(JDK8):https://blog.csdn.net/zhouhengzhe/article/details/113044800
参考
【1】一文读懂什么是SPI机制:https://mp.weixin.qq.com/s?src=11×tamp=1632879935&ver=3343&signature=9iLX9knXyTtDWvN0ur6ppVhj8ubzDV7IrvlOHQ6Wm9JpDwyFPzJqxiWgHtnR3CUa3Ej9tB1eKXhm4bC04mQuhY8shqwoi5-lLcWSUWEMSeAbxVJ9Pcso1ibZuXBQik&new=1
【2】Java堆外内存排查小结:https://mp.weixin.qq.com/s?src=11×tamp=1632886951&ver=3343&signature=XOmT1rR-p6H90i3B9UOa2e7dUGdvqrST2bDiQKMjU4W7puvXujxFDsn1SiTSz0QZh9chhsarNjHgz6IGqSPJEg-hWBLzTRTo-jDyHNncgAD59WoORwmoS0dHBvYhWH&new=1
【3】JVM 内存布局:堆区空间分配、新对象、Metaspace 元空间、栈帧:https://mp.weixin.qq.com/s?src=11×tamp=1632974557&ver=3345&signature=2bIRbjsrFeBiRMgCrwYr7dKmazSRtPtJT9azTDTJEgSrcSe11JSuampAFqOeBbInlIzwdXX1e2-o0rmCxxg0u4YmRUwBoC8jvdTSz-QVqMmCfOOeLtuayZ-QGX45&new=1
【4】手把手教你,线上服务频繁Full GC问题如何排查:https://mp.weixin.qq.com/s?src=11×tamp=1632973728&ver=3345&signature=KaREXdc0mckTpUuMcHQlTKOeHqqTi3uDwnCJxwEBRWMY-q1sY0BXttuOvr9tKjDiV0*SXTMhiC1-YaqnSLO9oopbWDeLrEIB5F5FD0zIgWx7ICesSXkRBGZ7dMW1LeMH&new=1