您的位置:首页 > 汽车 > 时评 > JVM 一些常见问题QA

JVM 一些常见问题QA

2024/7/7 8:13:55 来源:https://blog.csdn.net/jiezheee/article/details/139633747  浏览:    关键词:JVM 一些常见问题QA

GC Roots


  1. 虚拟机栈中引用的对象;

  2. 本地方法栈中JNI引用的对象;

  3. 方法区中类静态变量引用的对象;

  4. 方法区中常量引用的对象;

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参数配置


  1. -XX:PretenureSizeThreshold:对象大小超过设置的值,则直接分配在old区,默认为0,全部在eden分配。 此参数只对Serial及ParNew两款收集器有效。

  2. -XX:TargetSurvivorRatio=n:设置Survivor区的目标使用率,即当survivor区GC后使用率超过这个值,就可能会使较小的年龄的对象晋升;

活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小。可以通过GC日志中Full GC之后老年代数据大小得出,比较准确的方法是在程序稳定后,多次获取GC数据,通过取平均值的方式计算活跃数据的大小。活跃数据和各分区之间的比例关系如下:

    

Major GC要不要扫描年轻代?


  1. Serial,Parallel scavenge,Parallel old回收老年代的时候还会回收年轻代,Major GC升级为Full GC;

  2. CMS有两种模式

    1. 设置了-XX:+CMSScavengeBeforeRemark,回收老年代前会先回收年轻代;

    2. 没设置的话,会扫描年轻代,但只回收老年代;

  3. 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卡顿会对成功率造成影响;

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com