跳到主要内容

15、JVM 调优实战 - “Stop the World”问题分析

1. 新生代GC场景

新生代的内存分为Eden和两个Survivor的。如果系统不停的运行,最终会把Eden给塞满。

一旦Eden塞满,就会触发Minor GC,而进行垃圾回收是有专门的垃圾回收线程的,不同的内存区域会有不同的垃圾回收器 。

简单来说,就是垃圾回收线程和垃圾回收器配合起来,使用自己的垃圾回收算法,对指定的内存区域进行垃圾回收,如下图:

 

2. GC的时候还能继续创建新的对象吗?

这里要思考,在GC的时候,我们写好的 Java系统在运行期间还能不能继续在新生代里创建新的对象了?

假设允许在GC期间,还可以继续让系统在新生代的Eden区里创建新的对象。那么,对于程序新创建的这些对象,垃圾回收器是无法去持续追踪这些对象的状态的。

并且在进行垃圾回收的过程中把新对象中的存活对象也转移到 Survivor1或Survivor2中去的想法是不可行的。

所以,在垃圾回收的过程中,同时还允许我们写的Java系统继续不停的运行在Eden里持续创建新的对象,目前来看是不合适的一件事。

3. JVM的痛点:Stop the World

JVM最大的痛点,是在垃圾回收的这个过程。

因为在垃圾回收的时候,尽可能要让垃圾回收器专心致志的工作,所以不能随便让我们的Java系统创建新对象,此时JVM会在后台直接进入 “Stop the World” 状态。

也就是说,他们会直接停止我们写的Java系统的所有工作线程,让我们写的代码不再运行。

然后让垃圾回收线程可以专心致志的进行垃圾回收的工作,如下图所示:

 

这样的话,就可以让我们的系统暂停运行,然后不再创建新的对象,同时让垃圾回收线程尽快完成垃圾回收的工作,就是标记和转移Eden以及Survivor2的存活对象到Survivor1中去,然后尽快一次性回收掉Eden和Survivor2中的垃圾对象,如下图:

 

接着垃圾回收完毕,就可以继续恢复Java系统的工作线程的运行了,然后我们的代码就可以继续运行,继续在Eden中创建新的对象 

4. Stop the World造成的系统停顿

假设Minor GC要运行100ms,那么就会导致这100ms期间用户发起的所有请求都会出现短暂的卡顿,因为系统的工作线程不在运行,所以不能处理任何请求。

参考前面的案例,因为内存分配不合理,导致对象频繁进入老年代,平均七八分钟一次Full GC,而Full GC是最慢的,有的时候弄不好一次回收要进行几秒钟,甚至几十秒或几分钟。

那么一旦频繁的Full GC,就意味着系统每隔七八分钟后,会在30秒内任何用户的请求全部卡死无法处理,然后用户看到的都是系统超时之类的提示,这会让用户体验极差。

总得来说,无论是新生代GC还是老年代GC,都尽量不要让频率过高,也避免持续时间过长,避免影响系统正常运行。而这也是JVM过程中需要优化的一个重点。

5.不同的垃圾回收器的不同的影响

在对新生代的回收,Serial垃圾回收器就是用一个线程进行垃圾回收,然后暂停系统工作线程,所以一般很少用这种方式。

而平时常用的新生代垃圾回收区是ParNew,它针对服务器一般都是多核CPU做了优化,是支持多线程去垃圾回收的,可以大幅度提升回收的性能,缩短回收的时间。大致原理图如下:
 

综上所述,不同的垃圾回收器会有不同的机制和原理,使用多线程或者单线程,都是有区别的。