GC Roots
-
虚拟机栈中引用的对象;
-
本地方法栈中JNI引用的对象;
-
方法区中类静态变量引用的对象;
-
方法区中常量引用的对象;
Full GC是Minor GC+Major GC吗?
Minor GC:回收年轻代;
Major GC:回收老年代,经常会伴随至少一次的Minor GC;
Full GC:回收整个堆,年轻代+老年代;
新生代的S区动态年龄如何计算?
Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。例如:Survivor区 = 64M,desired survivor = 32M,此时Survivor区中age<=2的对象累计大小为41M,41M大于32M,所以晋升年龄阈值被设置为2,下次Minor GC时将年龄超过2的对象被晋升到老年代。
何时发生Minor GC和Full GC?(CMS为例)
- 何时触发Minor GC?
-
在系统需要在新生代Eden申请内存空间不足的时候触发,JVM会判断是否要先Major GC(最理想的就是直接复制到S1完事,内部解决,根本用不到Major GC)。
-
-
何时触发Major GC?
-
没开启担保机制,Minor GC前先进行一次Major GC;(1.7及以后默认开启了担保机制,1.6及以前则需要手动配置担保机制)
-
开启了担保机制,但平均晋升对象大小 > 老年代剩余空间;
-
设置了-XX:CMSInitiatingOccupancyFaction参数,后台会有一个线程定时扫描,如果老年代使用空间超过比值,也会执行Major GC。
-
-
何时触发Full GC?
-
CMS GC时出现promotion failed和concurrent mode failure(concurrent mode failure发生的原因一般是CMS正在进行,但是由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这时停止所有的线程,同时终止CMS,直接进行Serial Old GC);
-
主动触发Full GC(执行jmap -histo:live [pid])来避免碎片问题。
-
JVM参数配置
-
-XX:PretenureSizeThreshold:对象大小超过设置的值,则直接分配在old区,默认为0,全部在eden分配。 此参数只对Serial及ParNew两款收集器有效。
-
-XX:TargetSurvivorRatio=n:设置Survivor区的目标使用率,即当survivor区GC后使用率超过这个值,就可能会使较小的年龄的对象晋升;
活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下:
Major GC要不要扫描年轻代?
-
Serial,Parallel scavenge,Parallel old回收老年代的时候还会回收年轻代,Major GC升级为Full GC;
-
CMS有两种模式
-
设置了-XX:+CMSScavengeBeforeRemark,回收老年代前会先回收年轻代;
-
没设置的话,会扫描年轻代,但只回收老年代;
-
-
G1比较特殊,它无论处于何种模式下,都不需要扫描别的代,只需要处理一下记忆集;
Safepoint和OopMap
OopMap映射表记录哪些位置存放着对象引用,协助根节点快速完成枚举过程。
Safepoint是一些特定位置,当线程运行到这些位置时,线程中的某些状态是确定的。在safePoint可以记录OopMap信息,线程在safePoint停顿,虚拟机进行GC。
SafePoint一般出现在以下位置:循环体的结尾、方法返回前、调用方法的call之后、抛出异常的位置。这些位置保证线程不会长时间运行而无法到达safePoint,避免其他线程都停顿等待本线程。
JVM中的VMThread会一直等待直到VMOperationQueue中有操作请求出现,比如GC请求。而VMThread要开始工作必须要等到所有的Java线程进入到safepoint。JVM维护了一个数据结构,记录了所有的线程,所以它可以快速检查所有线程的状态。当有GC请求时,所有进入到safepoint的Java线程会在一个Thread_Lock锁阻塞,直到当JVM操作完成后,VM释放Thread_Lock,阻塞的Java线程才能继续运行。
safepoint只能处理正在运行的线程,它们可以主动运行到safepoint。而一些Sleep或者被blocked的线程则靠safe region来完成。线程进入到safe region的时候先标识自己进入了safe region,等它被唤醒准备离开safe region的时候,先检查能否离开,如果GC已经完成,那么可以离开,否则就在safe region呆着。
String.intern()原理
参考:深入解析String#intern
总结:jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:
-
将String常量池 从 Perm 区移动到了 Java Heap区
-
String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象(jdk1.6会在String常量池创建对象)
看懂这个代码,就算理解了
STW期间,新请求如何处理?
如果是RPC请求,很可能会在socket buffer上阻塞,IO读写暂停,比如刚好一个请求到来,就卡住等STW结束再进服务。
怎么排查线上GC问题 & GC优化
1,首先,在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。不能指望一个系统架构或代码有缺陷的应用,通过GC优化来飞跃;
2,其次,可以看出虚拟机内部已有很多优化来保证应用的稳定运行,所以不要为了调优而调优,不当的调优可能适得其反;
3,最后,GC优化是一个系统而复杂的工作,没有万能的调优策略可以满足所有的性能指标,比如可能不能同时满足低延时和高吞吐;
现象:服务抖动,成功率变低,GC耗时长。
1,看内存对象有无异常,大对象长时间存活;
jmap -histo:live PID
2,看是否cache过多,长时间存活:GC前后内存使用率对比;
3,如果GC要处理内存大也会更耗时,提前GC,提高频率降低每次清理内存;
// Old区使用了50%的时候触发GC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=50
4,增加对象在年轻代内存中驻留的时间,降低GC频率;
// 注意动态年龄问题,可能生效的不一定是配置的这个(详见CMS动态年龄)
-XX:MaxTenuringThreshold=31
5,调整年轻代比例;
如何选择各分区大小应该依赖应用程序中对象生命周期的分布情况:
如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。
经典比例参考:从实际案例聊聊Java应用的GC优化 - 美团技术团队
6,CMS-Remark之前强制进行年轻代GC;
// 这两个参数强制在remark阶段之前先进行一次年轻代GC,这样需要remark的内存量就不会太多(详见CMS跨带引用)
-XX:+ScavengeBeforeFullGC(Parallel GC的参数,ParNew不用配置)
-XX:+CMSScavengeBeforeRemark
Tips:
1.CMS-remark阶段需要对堆中所有的内存对象进行处理,如果在这个阶段之前强制执行一次年轻代的GC会大量减少remark需要处理的内存数量,进而降低JVM卡顿对成功率的影响;
2.对于Java HTTP服务,JVM的卡顿时间应该小于HTTP客户端的调用超时时间,否则JVM卡顿会对成功率造成影响;