持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
面试官:基于JVM运行的系统最怕什么?
年轻代GC
在JVM运行的时候,最核心的内存区域,其实就是堆内存,在这里会放各种我们系统中创建出来的对象。而且堆内存里通常都会划分为新生代和老年代两个内存区域,对象一般来说都是优先放在新生代的。
随着系统不停的运行,一定会导致越来越多的对象放入年轻代中,然后年轻代都快塞满了,放不下更多的对象了,毕竟内存都是有限的。这个时候就必须清理一下年轻代的垃圾对象,也就是那些没有GC Roots引用的对象。所谓的GC Roots就是类的静态变量,方法的局部变量。平时我们最经常创建对象的地方,就是在方法里,但是一旦一个方法运行完毕之后,方法的局部变量就没了,此时之前在方法里创建出来的对象就是垃圾了,没人引用了。所以在年轻代里,其实99%都是这种没人引用的垃圾对象。
在年轻代(也可以叫做新生代)快要塞满的时候,就会触发年轻代gc,也就是对年轻代进行垃圾回收,需要把年轻代里的垃圾对象都给回收掉。通过复制算法进行回收,通常来说新生代会有一块Eden区域用来创建对象,默认占据80%的内存,还有两块Survivor区域用来放垃圾回收后存活下来的对象,分别占据10%的内存。而且大家要注意一点,一旦要对新生代进行垃圾回收了,此时一定会停止系统程序的运行,不让系统程序执行任何代码逻辑了,这个叫做“Stop the World”。此时只能允许后台的垃圾回收器的多个垃圾回收线程去工作,执行垃圾回收。所谓的复制算法,说白了,就是对所有的GC Roots进行追踪,去标记出来所有被GC Roots直接或者间接引用的对象,他们就是存活对象。
接着就会把存活对象都转移到一块Survivor区域里去,就把存活的对象转移到一块Survivor区域里去了。接着就会直接把Eden区里的剩下的垃圾对象全部回收掉,释放内存空间,然后恢复系统程序的运行。看到这里,里有一个很大的问题,就是每次一旦年轻代塞满之后,在进行垃圾回收的时候,这个期间都必须停止系统程序的运行!这个就是基于JVM运行的系统最害怕的问题:系统卡顿问题!
假设一次年轻代垃圾回收需要20ms,那么就意味着在这20ms内,系统是无法工作的,此时用户对系统发送的请求,在这20ms内是无法处理的,需要卡住20ms。
面试官:年轻代gc到底多久一次对系统影响不大?
其实通常来说是不大的,其实年轻代gc几乎没什么好调优的,因为他的运行逻辑非常简单,就是Eden一旦满了无法放新对象就触发一次gc。一般来说,真要说对年轻代的gc进行调优,只要你给系统分配足够的内存即可,核心点还是在于堆内存的分配、新生代内存的分配。内存足够的话,通常来说系统可能在低峰时期在几个小时才有一次新生代gc,高峰期最多也就几分钟一次新生代gc。而且一般的业务系统都是部署在2核4G或者4核8G的机器上,此时分配给堆的内存不会超过3G,给新生代中的Eden区的内存也就1G左右。而且新生代采用的复制算法效率极高,因为新生代里存活的对象很少,只要迅速标记出这少量存活对象,移动到Survivor区,然后回收掉其他全部垃圾对象即可,速度很快。
很多时候,一次新生代gc可能也就耗费几毫秒,几十毫秒。大家设想一下,假如说你的系统运行着,然后每隔几分钟或者几十分钟执行一次新生代gc,系统卡顿几十毫秒,就这期间的请求会卡顿几十毫秒,几乎用户都是无感知的,所以新生代gc一般基本对系统性能影响不大。
面试官:什么时候新生代gc对系统影响很大?
当系统部署在大内存机器上的时候,比如说机器是32核64G的机器,此时分配给系统的内存有几十个G,新生代的Eden区可能30G~40G的内存。
比如类似Kafka、Elasticsearch之类的大数据相关的系统,都是部署在大内存的机器上的,此时如果系统负载非常的高,对于大数据系统是很有可能的,比如每秒几万的访问请求到Kafka、Elasticsearch上去。那么可能导致Eden区的几十G内存频繁塞满要触发垃圾回收,假设1分钟会塞满一次。
然后每次垃圾回收要停顿掉Kafka、Elasticsearch的运行,然后执行垃圾回收大概需要几秒钟,此时发现,可能每过一分钟,系统就要卡顿几秒钟,有的请求一旦卡死几秒钟就会超时报错,此时可能会导致系统频繁出错。
面试官:如何解决大内存机器的新生代GC过慢的问题?
那么如何解决这种几十G的大内存机器的新生代GC过慢的问题呢?用G1垃圾回收器。
我们针对G1垃圾回收器,可以设置一个期望的每次GC的停顿时间,比如我们可以设置一个20ms。那么G1基于他的Region内存划分原理,就可以在运行一段时间之后,比如就针对2G内存的Region进行垃圾回收,此时就仅仅停顿20ms,然后回收掉2G的内存空间,腾出来了部分内存,接着还可以继续让系统运行。
G1天生就适合这种大内存机器的JVM运行,可以完美解决大内存垃圾回收时间过长的问题。