JVM探索-1.JVM垃圾回收器及术语

时间:2022-10-25 23:00     作者:林立     分类: 作品


并行

  指两个或者多个事件在同一时刻发生,在现代计算机中通常指多台处理器上同时处理多个任务。

并发

  指两个或多个事件在同一时间间隔内发生,在现代计算机中一台处理器“同时”处理多个任务,那么这些任务只能交替运行,从处理器的角度上看任务只能串行执行,从用户的角度看这些任务“并行”执行,实际上是处理器根据一定的策略不断地切换执行这些“并行”的任务。
  即并发是在宏观上并行,在微观上切换执行。

JVM中的并行

  指多个垃圾回收相关线程在操作系统之上并发运行,这里的并行强调的是只有垃圾回收线程工作,程序线程都暂停执行,因此ParNew工作的时候一定发生了STW。...ParTask(如G1ParTask) 指的就是在这些任务运行的时候应用程序都必须暂停。

JVM中的并发

  指垃圾回收相关的线程并发运行(如果启动多个线程),这些线程也会和应用程序线程并发运行。...Concurrent...Thread(如ConcurrentG1RefineThread)就是指回收线程和Java应用程序同时运行。

STW

  Stop-the-world,直译就是停止一切,在JVM中指停止一切Java应用线程。

安全点(Safepoint)

  指JVM在执行一些操作的时需要STW,但并不是任何线程在任何地方都能进入STW,例如我们正在执行一段代码时,线程如何能够停止?设计安全点的目的是,当线程进入到安全点时,线程就会主动停止。

Mutator

  在很多英文文献和JVM源码中,经常看到这个单词,它指的是我们的Java应用线程。Mutator的含义是可变的,在这里的含义是因为线程运行,导致了内存的变化。垃圾回收过程中通常需要STW才能使Mutator暂停。

记忆集

  简称为RSet。主要记录不同代际对象的引用关系。

Evacuation

  转移、撤退或者回收,简称为Evac。在G1回收器中中指的是发现活跃对象,并将对象复制到新地址的过程。

GC Root

  垃圾回收的根。在JVM的垃圾回收过程中,需要从GC Root出发标记活跃对象,确保正在使用的对象在垃圾回收后都是存活的。

根集合(Root Set)。

  在JVM的垃圾回收过程中,需要从不同的GC Root出发,这些GC Root有线程栈、monitor列表、JNI对象等,而这些GC Root就构成了Root Set。

Full GC

  简称为FGC,整个堆的垃圾回收动作。通常Full GC是串行的,G1的Full GC不仅有串行实现,在JDK10及以后还有并行实现。

再标记(Remark)。

  指的是并发标记算法中,处理完并发标记后,需要更新并发标记中Mutator变更的引用,这一步需要STW。

垃圾回收

  垃圾回收(Garbage Collection,GC)指的是程序不用关心对象在内存中的生存周期,创建后只需要使用对象,不用关心何时释放以及如何释放对象,由JVM自动管理内存并释放这些对象所占用的空间。
  垃圾回收针对的是堆空间,目前垃圾回收算法主要有两类:
  1.引用计数法:在堆内存中分配对象时,会为对象分配一段额外的空间,这个空间用于维护一个计数器,如果对象增加了一个新的引用,则将增加计数器。如果一个引用关系失效则减少计数器。当一个对象的计数器变为0,则说明该对象已经被废弃,处于不活跃状态,可以被回收。引用计数法需要解决循环依赖的问题,比如Python语言里,垃圾回收就使用了引用计数法。
  2.可达性分析法(根引用分析法),基本思路就是将根集合作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象没有被任何引用链访问到时,则证明此对象是不活跃的,可以被回收。
  两种算法各有优缺点,JVM的垃圾回收采用了可达性分析法。
  垃圾回收算法实现主要分为复制(Copy)、标记清除(Mark-Sweep)和标记压缩(Mark-Compact)。
  在回收方法上又可以分为串行回收、并行回收、并发回收。
  在内存管理上可以分为分代管理和非分代管理。

分代管理

  分代管理就是把内存划分成不同的区域进行管理,有些对象存活的时间短,有些对象存活的时间长,把存活时间短的对象放在一个区域管理,把存活时间长的对象放在另一个区域管理。那么可以为两个不同的区域选择不同的算法,加快垃圾回收的效率。我们假定内存被划分成2个代:新生代和老生代。把容易死亡的对象放在新生代,通常采用复制算法回收;把预期存活时间较长的对象放在老生代,通常采用标记清除算法。

复制算法

  复制算法其实就是在内存间进行分区,此分区的数量可以是两个或多个,根据对象的活跃程度不同将不同分区的活跃对象按照某种计算方式在分区间进行复制。使用两个分区时内存的利用率只有50%;使用多个分区(如3个分区),则可以提高内存的使用率。
  此处演示把堆空间分为1个新生代(分为3个分区:Eden、Survivor0、Survivor1)、1个老生代的收集过程。
  普通对象创建的时候都是放在Eden区,S0和S1分别是两个存活区。第一次垃圾收集前S0和S1都为空,在垃圾收集后,Eden和S0里面的活跃对象(即可以通过根集合到达的对象)都放入了S1区:

  回收后Mutator继续运行并产生垃圾,在第二次运行前Eden和S1都有活跃对象,在垃圾收集后,Eden和S1里面的活跃对象(即可以通过根节点到达的对象)都被放入到S0区,一直这样循环收集:

  满足最活跃条件(即存活次数最多)的对象晋升老生代区。

标记清除

  从根集合出发,遍历对象,把活跃对象入栈,并依次处理。处理方式可以是广度优先搜索也可以是深度优先搜索(通常使用深度优先搜索,节约内存)。标记出活跃对象之后,就可以把不活跃对象清除。

  这里仅仅演示了如何找到对象,没有进一步介绍找到对象后如何处理。对于标记清除算法其实还需要额外的数据结构(比如一个链表)来记录可用空间,在对象分配的时候从这个链表中寻找能够容纳对象的空间。当然这里还有很多细节都未涉及,比如在分配时如何找到最合适的内存空间,有First Fit、Best Fit和Worst Fit等方法,这里不再赘述。

标记压缩

  标记压缩算法是为了解决标记清除算法中使内存碎片化的问题,除了上述的标记动作之外,还会把活跃对象重新整理从头开始排列,减少内存碎片。

JVM垃圾回收

  为了达到最大性能,基于分代管理和回收算法,结合回收的时机,JVM实现了垃圾回收器:串行回收、并行回收、并发标记回收(CMS)和垃圾优先回收。

串行回收

  串行回收使用单线程进行垃圾回收,在回收的时候Mutator需要STW。新生代通常采用复制算法,老生代通常采用标记压缩算法。

并行回收

  并行回收使用多线程进行垃圾回收,在回收的时候Mutator需要暂停,新生代通常采用复制算法,老生代通常采用标记压缩算法。

并发标记回收

  并发标记回收(CMS)的整个回收期间划分成多个阶段:初始标记、并发标记、重新标记、并发清除等。在初始标记和重新标记阶段需要暂停Mutator,在并发标记和并发清除期间可以和Mutator并发运行。这个算法通常适用于老生代,新生代可以采用并行回收。

垃圾优先回收

  垃圾优先回收器(Garbage-First,也称为G1)从JDK7 Update 4开始正式提供。G1致力于在多CPU和大内存服务器上对垃圾回收提供软实时目标和高吞吐量。G1垃圾回收器的设计和前3种回收器都不一样,它们在并行、串行以及CMS GC针对堆空间的管理方式上都是连续的。

  连续的内存将导致垃圾回收时收集时间过长,停顿时间不可控。因此G1将堆拆成一系列的分区(Heap Region),这样在一个时间段内,大部分的垃圾收集操作只针对一部分分区,而不是整个堆或整个(老生)代。

  在G1里,新生代就是一系列的内存分区,这意味着不用再要求新生代是一个连续的内存块。类似地,老生代也是由一系列的分区组成。这样也就不需要在JVM运行时考虑哪些分区是老生代,哪些是新生代。
  事实上,G1通常的运行状态是:映射G1分区的虚拟内存随着时间的推移在不同的代之间切换。例如一个G1分区最初被指定为新生代,经过一次新生代的回收之后,会将整个新生代分区都划入未使用的分区中,那它可以作为新生代分区使用,也可以作为老生代分区使用。很可能在完成一个新生代收集之后,一个新生代的分区在未来的某个时刻可用于老生代分区。同样,在一个老生代分区完成收集之后,它就成为了可用分区,在未来某个时候可作为一个新生代分区来使用。
  G1新生代的收集方式是并行收集,采用复制算法。与其他JVM垃圾回收器一样,一旦发生一次新生代回收,整个新生代都会被回收,这也就是我们常说的新生代回收(Young GC)。但是G1和其他垃圾回收器不同的地方在于:G1会根据预测时间动态改变新生代的大小。其他垃圾回收新生代的大小也可以动态变化,但这个变化主要是根据内存的使用情况进行的。G1中则是以预测时间为导向,根据内存的使用情况调整新生代分区的数目。
  G1老生代的垃圾回收方式与其他JVM垃圾回收器对老生代处理有着极大的不同。G1老生代的收集不会为了释放老生代的空间对整个老生代做回收。相反,在任意时刻只有一部分老生代分区会被回收,并且,这部分老生代分区将在下一次增量回收时与所有的新生代分区一起被收集。这就是我们所说的混合回收(Mixed GC)。在选择老生代分区的时候,优先考虑垃圾多的分区,这也正是垃圾优先这个名字的由来。
  G1老生代的垃圾回收方式与其他JVM垃圾回收器对老生代处理有着极大的不同。G1老生代的收集不会为了释放老生代的空间对整个老生代做回收。相反,在任意时刻只有一部分老生代分区会被回收,并且,这部分老生代分区将在下一次增量回收时与所有的新生代分区一起被收集。这就是我们所说的混合回收(Mixed GC)。在选择老生代分区的时候,优先考虑垃圾多的分区,这也正是垃圾优先这个名字的由来。
  从实现角度来看,G1算法是复合算法,吸收了以下算法的优势:
  1.列车算法,对内存进行多分区。
  2.CMS,对分区进行并发标记。
  3.最老优先,最老的数据(通常也是垃圾)优先收集。