最近刚刚将本身的一个应用从CMS升级到G1,在一天早上,刚刚到办公室坐下,就收到手机一阵报警,去查看了监控,发现机器的内存出现了一个90度的涨幅,以下图所示:java
在查看GC日志后,发现那个时间点附近出现了“to-space exhausted”这种日志(关于G1的日志学习,参见我以前的文章:【译】深刻理解G1的GC日志(一)))面试
在这里,我比较奇怪的是为啥to-sapce exhausted会致使整个机器的内存激增。咱们JVM团队同窗给个人解释是:老区不够了,这个时候会把young区全部对象无论死活都转成old区对象,因此总的内存使用量会暴增。这一个知识点,我以前学习G1的时候还真没有get到(关于G1的基本知识,参见以前的文章:多是最全面的G1学习笔记))。后端
不过,我有另一个疑问:xmx和xms相同的话堆空间应该不变,一开始就分配5g,而后加上非堆内存,那么java进程起来后就会超过5g,这是没问题的;可是这里利用空闲的内存也应该是利用堆上的空间,而后总体的内存块应该已经分配出去了,应该不会出现机器内存激增的状况。JVM团队的同窗给我解释道:没有,第一次读写到了才会实际从os分配出来物理内存。并发
针对上面的问题,咱们最终肯定了下面的调优建议:性能
基于上面这个问题,我又去找了一些资料,整理以下。学习
在这本书的123页有提到,上面这种状况属于晋升失败的状况——G1收集器完成了标记阶段,开始启动混合式垃圾收集,准备要清理老年代分区,可是老年代分区在垃圾收集器释放出足够的空间以前就已经被耗尽了。这种失败一般意味着混合式垃圾收集须要更迅速得完成垃圾收集,每次新生代垃圾收集须要处理更多的老年代分区。通常来讲,一系列的to-space exhausted以后会跟着一次FGC。优化
在咱们上面的这个例子中,是old区的使用速度超过了垃圾收集器的回收速度,所以能够考虑两种调优的思路spa
-XX:InitiatingHeapOccupancyPercent=N
这个参数,默认状况下该参数是45(PS:这个参数表示的是占用整个堆内存的比例),不过,这个参数也不能调得过小,不然会致使过多的并发收集周期和混合式垃圾收集,给应用早成过多的停顿。 -XX:G1MixedGCCountTarget=N
参数能够控制每一个混合式周期中回收的Old分区数量,该参数的默认值是8; 要特别关注日志片断中的"to-space exhausted"和“Evacuation Failure”两个日志,以下图所示。能够看出,Evacuation Failure消耗了684.1ms,也就是说,此次转移失败致使了将近1s的应用暂停。线程
这种状况属于转移失败,这本书给出了两点建议:3d
-XX:InitiatingHeapOccupancyPercent=N
这个参数的值,由于转移失败的代价比多执行一些并发标记周期高不少 -XX:ConcGCThreads
,增长用于垃圾收集的线程个数,代价是会多一些CPU的消耗;也就是会占用Java应用的CPU时间,这一点也须要权衡一下。 -XX:G1ReservePercent
的大小,在G1中这个默认值是10%。 JVM参数的调优,是一个不断推导和尝试的过程,其中最重要的数据就是GC日志和Java堆内存快照,所以:(1)在JVM参数中必定要设置HeapDumpAfterFullGC和HeapDumpOnOutOfMemoryError两个参数,能够在发送FGC和OOM的时候将当时的Java堆状况记录下来,用于过后分析;(2)GC日志要单独打印到一个日志文件中,方便分析,若是不特别设置,GC日志会打印到stdout.log中,会有其余的日志混合在中间,影响问题排查。
JVM的参数调优并非万能的,发生OOM或者FGC的时候,业务代码中也必定有不合理的地方,须要作合理的限制和优化,不能将全部的事情都交给JVM抗。***本号专一于后端技术、JVM问题排查和优化、Java面试题、我的成长和自我管理等主题,为读者提供一线开发者的工做和成长经验,期待你能在这里有所收获。