JVM系列之STW、并行与并发、安全点与安全区域

前言

在平时实际工作中,其实接触 JVM 的机会比较少,而学习 JVM 时,大部分时候也是看书或者网上看博客教程,然而在学习过程中,经常会碰到很多陌生的名词,难以理解,尤其是在垃圾回收器中,因此本文找了几个和垃圾回收器相关知识中联系比较紧密的名词解释一下。

另外送给大家小编整理的JVM大礼包,话不多说,直接上图。

JVM系列之STW、并行与并发、安全点与安全区域_第1张图片

 

JVM系列之STW、并行与并发、安全点与安全区域_第2张图片

 

JVM系列之STW、并行与并发、安全点与安全区域_第3张图片

 

需要免费获取这些资料的小伙伴请在关注点赞之后私信我获取哦。

Stop The World

「Stop The World」 简称 STW,它指的是程序在运行过程中,在发生 GC 的过程中,会让所有的用户线程都暂停下来,此时对应用程序而言,相当于整个程序都停止运行了,因此称之为 Stop The World。STW 这个过程是 JVM 在后台自动发起和自动完成的,在用户不可见的情况下,将所有应用程序停顿下来。

尽管目前存在很多垃圾回收器,并且新的垃圾回收器的效率越来越高,但是目前为止,所有的垃圾回收器都无法避免 STW,即使是号称低延时的 「CMS」 和 「G1」 垃圾回收器,也存在 STW。这是因为在标记阶段,使用可达性分析算法进行分析时,整个应用程序的数据都应该处于「一致性视图」当中,这是为了保证可达性分析算法的准确性,因此需要暂停所有的用户线程。

显然 STW 对应用程序十分不友好,「STW 的时间越长,应用程序卡顿的时间就越长,我们应该尽量减少 STW 的次数,以及每次 STW 的时间」

程序中的并行与并发

通常情况下,在操作系统中,我们所说的「并行」指的是在多个 CPU 上(或者说多核),多个线程或者进程「同时」运行,这些线程相互之间因为运行在不同的 CPU 上,因此不会产生竞争资源的现象。如下图所示,A、B、C 为三个进程,同一时刻,它们是「真正意义上的同时运行」

JVM系列之STW、并行与并发、安全点与安全区域_第4张图片

 

「并发」指的是在同一个 CPU 上,严格来说是在同一个处理器上,「一段时间内」,同时运行着多个线程或者进程。众所周知,CPU 执行任务是分时间片来执行的,在一个时间片内只会有一个线程运行,因此「并发不是真正意义上的同时运行」。而因为每一个时间片特别小,小到给人们的感觉是这几个线程都没有间断过,而是多个线程在同时运行。

如下如所示,A、B、C 为三个线程,在一个时间片内,只会有一个线程在执行。所以说,并发指的是在一段时间内,多个线程在同一个 CPU 上运行,这里需要强调地是「一段时间内」

JVM系列之STW、并行与并发、安全点与安全区域_第5张图片

 

在并发情况下,因为是多个线程运行在同一个 CPU 上,因此它们相互之间是竞争关系,会争夺 CPU 资源。

垃圾回收器中的并行与并发

与程序中的并行、并发不一样的是,「垃圾回收器中的并行」(Parallel)指的是在同一时刻,有「多个垃圾回收线程」在多个 CPU 上同时执行,在垃圾回收线程执行期间,用户线程会全部暂停,因此会产生 STW。在 7 中经典的垃圾回收器中,「ParNew、Parallel Scavenge、Parallel Old」 这几个垃圾回收器就是属于并行的垃圾回收器。

与并行相对的就是「串行」(Serial),在单核的 CPU 上,只有一个垃圾回收线程执行,且在垃圾回收线程执行期间,用户线程也会暂停,因此也会产生 STW。「Serial、Serail Old」 这两个垃圾回收器就是属于串行的垃圾回收器。垃圾回收器中的并行与串行的示意图如下。

JVM系列之STW、并行与并发、安全点与安全区域_第6张图片

 

「垃圾回收器中的并发」指的是「垃圾回收线程和用户线程同时执行」(这里的「同时」,指的也是在一个时间段内),在多个 CPU 上场景下,用户线程运行在一个 CPU 上,垃圾回收线程运行在另一个 CPU 上,此时垃圾回收线程和用户线程是处于并行状态。并发的垃圾回收器的示意图如下。

JVM系列之STW、并行与并发、安全点与安全区域_第7张图片

 

并发的垃圾回收器在进行垃圾回收过程中,从开始到结束,垃圾回收线程和用户线程并不是全程都是并行的,也会出现交替执行,因此也会出现 STW 的现象。为什么会这样呢?这是由底层的垃圾回收算法导致的,因为在标记阶段,为了保证标记的正确性,必须要停止用户线程。

在 7 种经典的垃圾回收器中,并发的垃圾回收器的代表有 「CMS、G1」

安全点

在程序执行的过程中,并不是在任何地方都能进行 GC,而是必须得在「安全点(Safepoint)」 处才能进行。

安全点的选择十分重要,如果程序在执行过程中,安全点过多,GC 可能过于频繁,那么就可能导致程序运行时的性能下降,而如果安全点过少,那么就会导致系统需要间隔好长时间才能执行一次 GC,这样每次 GC 需要回收的垃圾过多,导致挺多的时间会很长。

那么在程序中,什么样的地方才能被称之为安全点呢?实际上,选取安全点的标准是「是否具有让程序长时间执行的特征」,因为大部分指令的运行时间都很短,我们可以接受让这些指令执行完以后再进行垃圾回收,而如果某个指令执行时间很长,程序如果让这些指令执行完再进行垃圾回收,这样就可能导致垃圾回收进行得不够及时。

因此通常会将如「方法调用、循环跳转、异常跳转」等指令序列复用作为安全点。

现在我们知道了程序在遇到安全点后会停下来进行 GC,但问题来了,一个应用程序中,同一时刻可能有很多个线程在执行,但是所有线程在同一时刻都遇到安全点的概率太小了。通常情况下,当要发生 GC 时,可能是其中一部分线程遇到了安全点,而另一部分线程还没到安全点,那这个时候该怎么办呢?答案是让其他线程也都跑到最近的安全点停下来。

而让其他线程都跑到最近点的安全点停下来有两种做法:

  1. 「抢占式中断」,也就是中断所有的线程,然后再判断每个线程是否在安全点,如果不在,就恢复线程,让线程继续运行,直到跑到最近的安全点,这种方式目前已经没有虚拟机在采用了。
  2. 「主动式中断」,当要发生 GC 时,设置一个标志位,然后其他线程运行到安全点时,先判断这个标志位,如果标志位的值表示此时需要进行 GC,那么线程就停下来,将自己中断挂起,否则继续运行。

安全区域

安全点的存在使得程序在运行时,在不太长的时间内,就会遇到可以进入 GC 的安全点,但是如果遇到线程”不执行“的情况该怎么办呢?例如线程在 Sleep 或者 Blocked,我们不可能让其他线程都等在那儿,然后等待这个线程从 Sleep 或者 Blocked 中醒来,再运行到安全点,因为你无法确定这个线程会 Sleep 或者 Blocked 多长时间。

为了解决这种情况,「安全区域(Safe Region)」 这个概念就出现了,安全区域是指:在一个代码片段内,对象之间的引用关系不会发生变化,那么在这个区域中的任何位置进行垃圾回收都是没问题的。

在实际执行过程中,当线程进入到安全区域时,会先将自己标识为进入 Safe Region 状态,那么当需要进行 GC 时,JVM 会忽略已经进入到 Safe Region 的线程;当线程要离开安全区域时,会判断 GC 是否完成,如果 GC 已经完成,则可以离开 Safe Region,否则继续等待,直到接收到 GC 完成的信号。

总结

本文主要介绍了在操作系统中和在垃圾回收器中,对并行与并发这两个概念的理解,这对后面即将介绍的 7 款垃圾回收器会有很大帮助。另外还介绍了安全点和安全区域两个概念,它们的存在是为了告诉线程,当需要发生 GC 时,它们可以在哪些地方停顿下来。

作者:天堂同志
链接:https://juejin.im/post/5eea4ade6fb9a058805983ab

最后再提醒一下大家资料免费获取方式,大家关注点赞之后私信我获取哦~

你可能感兴趣的:(java,JVM,java,编程语言,多线程,jvm,面试)