JVM Series (three): java garbage collection mechanism

java garbage collection mechanism introduced

   Previous tells the JVM memory model, to understand most of the objects are allocated in the heap above, we did not specify which objects are displayed in the coding of the recovery when needed, but the program is in operation in the process would have been create an object, the reason is because we no memory overflow of the virtual machine to help me we automatically garbage collection, to ensure the program runs there is enough space to allocate objects we create.
   JVM memory is divided into five regions, where the program counter, the virtual machine stack, native method stacks thread is privately owned, with the memory of the destruction of the thread exits. Heap and method area is dynamically allocated, the effect is due to garbage collection method has little area, this chapter said garbage collection mainly refers to the heap of garbage collection.

What kind of objects can be recycled

   What kind of objects can be recycled it? Imagine our lives, what kind of things we will be thrown into the trash it is not something already no longer in use or that there is nothing of value in, in java is the same, that is no longer used to object. So in java, how to determine the object is not no longer be used? Obviously, this seems better than real life thing is to determine which garbage is much complicated.

How to determine an object is garbage

As mentioned earlier, we need to know which objects need to be recovered, then how to determine whether the object needs to recover it?

Reference counting.

   When an object is created, add a reference to an object counter, place whenever there is a reference to the time, give counter is incremented when referring to fail, give counter is decremented when the reference counter is 0, indicating that the object does not It will no longer be used. This method is called reference counting. Reference counting logic is relatively simple, high efficiency, but it can not solve the problem of circulation between the object and object references.

Reachability analysis algorithm

   The basic idea of reachability analysis algorithm is to search down through the starting point is known as GC Roots, search through the chain link is called a reference, without any link to reach this target, the object will not again used, it can be recovered.
In the java language, the following objects can be called GC Roots:

  • VM stack referenced objects,
  • Static properties of the class of the object referenced method area
  • The method of constant reference object region
  • Native method stacks of objects Native method references.

As shown in FIG: Object1, Object2, Object3 is reachable by Gc Roos, so these objects can not be recycled, and Object4, Object5 unreachable by GC Roots, these objects can be recycled.

                                                               

Garbage collection algorithm

1. Mark - sweep algorithm (Mark-Sweep)

Clear labeling algorithm is the most basic, it is divided into two phases, marked and cleared. Marking the first recovery of an object, and then clear the memory of this part of the object.

Mark phase heap all objects will be scanned again to determine the objects need to be recovered, more time-consuming.

                               
Disadvantages:
(1) mark and sweep the two processes are time-consuming, inefficient
(2) will produce a large number of discrete memory fragmentation, space debris could cause too much time in the future need to allocate large objects in the program is running We can not find enough contiguous memory and had to trigger another garbage collection operation in advance.

2. Copy - collection algorithm

   Copy the recovery, by definition, it is to copy the live objects out, and then clean up the rest of the memory. This algorithm does not produce memory fragmentation. The memory is divided into two equal areas, the live objects are copied directly to another block of memory allocation equal reason, because in extreme cases, the object of an area of ​​memory is alive. But this memory utilization is very low, and later through the new generation of research objects are basically survival rate is relatively low, 98% of the basic objects will be recovered out when garbage collection. Therefore, the new generation is divided into three regions, Eden area, and survivor1 survivor0 area, according to the default 8: 1: 1 assignment ratio, Eden zone after recovery, to copy live objects survivor0 area, so would be only 10 % of the space is not used to, but we can not guarantee that every recycled objects are less than 10%, therefore, when the survivor space is not enough, we need to rely on other memory space.

                                  

3. Mark - Collation Algorithm

   Copy - collection algorithm in the object survive very high efficiency at relatively few cases, but when the high survival rate when the object is not suitable for use. Mark - sorting algorithm and mark cleared somewhat similar, are the first mark, but the mark - sorting algorithm will be recyclable objects are moved to the end, then clean out directly recyclable objects other than the object boundary. The advantage is that no memory fragmentation.

                                  

4.分代收集算法

   其实这种算法可以看做是前几个算法的结合,根据对象存活的特点,将堆分为新生代和老年代。新生代的对象存活率低,存活对象少,使用复制回收算法的效率高,而老年代对象存活率高,存活对象多,显然是使用标记整理的算法效率高。

java堆的内存模型

   前面提到,根据对象存活的特点以及使垃圾回收产生算法产生最大的收益,将堆区分为两大块,一个是Old区,一个是Young区。Young区分为两大块,一个是Survivor区(S0+S1),一块是Eden区。 S0和S1一样大,也可以叫From和To。

对象创建所在区域
   一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。
比如有对象A,B,C等创建在Eden区,但是Eden区的内存空间肯定有限,比如有100M,假如已经使用了
100M或者达到一个设定的临界值,这时候就需要对Eden内存空间进行清理,即垃圾收集(Garbage Collect),
这样的GC我们称之为Minor GC,Minor GC指得是Young区的GC。经过GC之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活着的对象需要将其复制到Survivor区,然后再清空Eden区中的这些对象。

为什么要分为surivor0和surivor1

   下面根据垃圾收集算法,详细讲解下为什么要分为surivor0和surivor1,难道一个survivor区不行吗?
   假设只有一个s0区,eden区回收之后,一部分对象存放到了s0区,此时eden区空间全部释放,内存都是连续的。但是因为s0区也会进行垃圾回收,它有一部分存活的对象进入到了Old区,还有一部分对象存活留下来,这时候s0区就产生了内存碎片,为了使s0区的内存空间相对连续,再分配一个s1区,大小和s0一样,每次垃圾回收的时候,将eden区和s0区存活的对象移动到s1区,这样永远都能保证s0或者s1的内存空间是连续的。当然,这样的情况下会使得s0或者s1区有一个空间永远为空,浪费10%的内存空间,当然为了最大化的利用young区,这样的浪费是被接受的。所以,young区一次GC流程是这样的:在同一个时间点上,S0和S1只能有一个区有数据,另外一个是空的。假设s0区有数据,此时进行一次GC操作,s0区中对象的年龄就会+1,而Eden区中所有存活的对象会被复制到是s1区,s0区中还能存活的对象会有两个去处。若对象年龄达到之前设置好的年龄阈值,此时对象会被移动到Old区,Eden区和s0区没有达到阈值的对象会被复制到s1区,s0区将又会变为空的。

整个young区的回收过程是这样的:

                                             

一个对象的一生


   我是一个普通的对象,我出生在Eden区,周围还有一些和我长得很像的兄弟姐妹,我在Eden区玩了一段时间后,后来我的兄弟们越来越多,多到住不下了,于是我的JVM爸爸就把我赶出了Eden区,我被发配到了s0区,在s0区,我认识了一个女生Baby,她说它的故乡也是Eden区,她比我早来几年,我们互相心生好感,我们彼此约定白头偕老,在这段蜜月期我们时不时的从s0区逛到s1区,又从s1区逛到s0区,可是好景不长,有一天早上醒来,我发现我的Baby不见了,卧槽不见了,我抓狂,她给我留了个字条,说n年后去Old区找她。我很伤心,但是我一直觉得老天用一根无形的丝线将我们联系在一起。我想了一下,两情若是久长时,又岂在朝朝暮暮,我心里有她就行。n多年过去了,我一直记得这个事情,这一天终于到来了,我立即收拾包袱来到了Old区,找了许久,可当我找到她的时候,她已白发苍苍,行将就木,她说她终于等到我了,我要是晚来几分钟连她最后一面都见不到。说完她就拜拜了,身体消散在Old区,我心里已然了无牵挂,决定追随我的爱人,于是我也消散在这片天空,泯然于世间,仿佛我从来没有来过一样。


Minor GC、Major GC、Full GC

   新生代的垃圾回收叫Minor GC,老年代的垃圾回收叫Major GC,Full GC是指清理整个堆空间,包括年新生代和老年代。由于老年代大部分场景是由新生代垃圾回收触发,所以,Major GC通常也会伴随着一次Minor GC。

八种垃圾收集器

   前面讲到了垃圾收集的算法,这只是一种理论思想,我们需要把思想转化为一种具体的垃圾收集工具,垃圾收集器就是垃圾收集算法的具体实现。它们分别是新生代的:Serial、ParNew、Parallel Scavenge 老年代的:Serial Old、 Parallel Old、CMS以及适用于新生代和老年代的G1。算上jdk11的ZGC目前一共是八种垃圾收集器。目前现代互联网公司基本都采用CMS和G1作为线上的垃圾收集器,因此本文后续篇幅将会着重介绍这两个垃圾收集器。

Serial收集器

Serial是最早的垃圾收集器,这是一个单线程收集器,它只适用一个CPU或者是一条收集线去执行回收任务。
                                             

   如图所示,Serial收集器在工作的时候必须暂停所有的用户线程,也就是 STW(Stop The World),用户线程必须在收集任务完成之后才能工作。如果回收的时间过长的话是很影响用户体验的。Serial适用于单个CPU的环境,其实随着计算机的发展,如今多核CPU已经很普遍,就算是个人的PC也是多核的更别说线上的服务器了,所以个人认为Serial以后使用的场景将会非常少。

2.ParNew收集器

   ParNew是一个新生代的多线程的收集器,它相当于是Serial的多线程版本。它的一些参数配置和Serial基本完全相同。只不过ParNew收集器在工作的时候,是多个线程工作的,如图所示:

                             

   ParNew适合在多个CPU场景下使用,而我们的线上服务器基本都是多核CPU,所以,使用新生代的ParNew搭配老年代的CMS收集器还是挺常见的,我所在的部门的系统线上就是使用的ParNew+CMS组合。与Serial相同的是,ParNew在进行垃圾回收的时候,也会暂停所用的用户线程。

3.Parallel Scavenge收集器

   Parallel Scavenge 也是一个新生代收集器,并且也是一个多线程收集器,Parallel Scavenge关注的点是应用的吞吐量,吞吐量 = 用户代码运行时间/用户运行代码时间+GC时间,它提供了两个参数用来控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数和直接设置吞吐量大小的-XX:GCTimeRatio 参数。GCTimeRatio参数的值是一个大于0且小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。高吞吐量可以高效的利用CPU时间,尽快完成计算任务,因此,Parallel Scavenge收集器也用于需要密集计算不需要进行用户交互的一些后台。

4.Serival Old收集器

Serival Old 收集器是垃圾收集的老年代版本,也是一个单线程收集器。

5.Parallel Old收集器

   Parallel Old收集器是Parallel Scavenge的老年代版本。可以使用Parallel Scavenge+Parallel Old组合,在注重吞吐量和CPU资源敏感的场合可以优先考虑Parallell SCavenge 和Parallell Old组合。
                           

6.CMS(Concurrent Mark Sweep) 收集器

   CMS(Concurrent Mark Sweep),并发标记清除,这是一种追求低停顿时间为的收集器。互联网时代,用户体验为王,垃圾收集的时间越短,给用户带来的体验就越好。CMS收集器整个回收过程可以分为四个步骤:

  • 初始标记(CMS inint mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark mark)
  • 并发清除(CMS concurrent sweep)
    如图所示:
                               

初始标记
   初始标记只是标记着GC Roots 能直接关联到的对象,这个过程需要对所有的对象进行标记,为了防止标记的过程中有对象的状态发生改变,需要暂停用户线程,因为只是标记GC Roots 能直接关联到的对象,因此这部分的执行速度很快。

并发标记:
对初始标记中标记的存活对象进行trace,标记这些对象为可达对象,这个阶段在标记的时候可以执行用户线程,由于用户线程会和标记的线程一起工作,可能会有新的垃圾对象产生而没有标记完整。所以会将在并发阶段新生代晋升到老年代的对象、直接在老年代分配的对象以及老年代引用关系发生变化的对象所在的card标记为dirty,避免在重新标记阶段扫描整个老年代。

重新标记:
   重新标记阶段是为了修正并发标记阶段产生的垃圾对象,这一部分是暂停用户线程的,但是执行时间也很快。

并发清除:
   这个阶段是是清除标记好的垃圾对象,会和用户线程同时进行。

   cms垃圾收集允许一定的误差,因为并发标清除的阶段会有用户线程同时工作,又将会有新的垃圾对象产生。但是它主要考虑的是低停顿时间。由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。

cms收集器很好的展示了它的优点,低停顿,但是,它也存在着以下几个缺点。

  • 吞吐量降低:由于是和用户线程并行执行的,会占用一部分的CPU资源,会导致用户进程变慢影响吞吐量,这也是和Parallel Old相反的地方。
  • 产生浮动垃圾:什么是浮动垃圾,前面也提到了,在并发清理的阶段,由于清理的工作是和用户线程一起工作的,那么就会在清理的阶段而再次产生垃圾对象,但是前面的标记阶段已经结束,所以清理阶段是无法清除这些新产生的垃圾对象的,只能等待下一次的垃圾回收,所以,就必须要留有一部分的内存空间给这些对象存储。如果预留空间不够的话,会出现“Concurrent Model Failure”,这时虚拟机会临时启用Serial Old收集器来收集,这样就会造成停顿时间过长。
  • 会产生内存碎片:由于CMS是采用标记-清除算法来实现的,由前面的图可知,标记清除算法会使内存空间不连续,如果有大的对象分配过来而刚好又没有足够的连续空间存储的话就会再次触发Full GC。为了解决这个问题CMS提供了参数-XX:+UseCMSCompactAtFullCollection 来在Full GC之前进行压缩空间,但是这不得不导致停顿时间变长。

G1(GarBage-First)收集器

   G1收集器是一款面向服务端的收集器,也就是说,它将低停顿时间作为终极目标。G1与其他垃圾收集器的区别是它可以控制垃圾收集时间在某一个范围之内。与CMS垃圾收集的运行过程类似,它分为初始标记,并发标记,最终标记,筛选回收。G1之所以能够将停顿时间控制在一个指定的时间内,就是因为它可以选择性的进行回收。

   G1尝试着去满足最小的停顿时间,在G1中,停顿时间是可以设置的,是可控制的,之所以可以建立可预测的停顿时间模型,是因为G1避免了在java堆中进行全区域的垃圾收集。传统的新生代老年代的内存模型被多个大小相等的独立区域(Region)所取代。如下图所示,虽然新生代和老年代的概念还保留着,但是他们不再是物理隔离的了,他们都是由Region所组成。G1在清除阶段是有选择性的,它会根据设置的停顿时间,选择回报率最大的Region。Region可以说是G1回收器一次回收的最小单元。即每一次回收都是回收N个Region。这个N是多少,主要受到G1回收的效率和用户设置的软实时目标有关。
G1的内存布局:

                                 

G1中的巨型对象是指,占用了Region容量的50%以上的一个对象。Humongous区,就专门用来存储巨型对象。如果一个H区装不下一个巨型对象,则会通过连续的若干H分区来存储。因为巨型对象的转移会影响GC效率,所以并发标记阶段发现巨型对象不再存活时,会将其直接回收。分区可以有效利用内存空间,因为收集整体是使用“标记-整理”,Region之间基于“复制”算法,GC后会将存活对象复制到可用分区(未分配的分区),所以不会产生空间碎片。

   前面说到,G1会选择性的回收Region,避免扫描整个堆。但是正常情况下,每一个Region之间可能都会有互相引用的对象,这样的话在垃圾收集扫描的时候还是不可避免的扫描整个堆来确定哪些是垃圾对象,G1是如何解决这一问题的呢?G1通过让每一个Region都维护一个Remembered Set来避免全堆扫描,在程序对引用类型的对象进行写操作的时候,虚拟机会检查Reference引用对象是否在不同的Region,并且会把这些引用的信息记录在Renembered Set中。

整个G1的垃圾回收阶段可以分为:
初始标记:标记GC Roots能直接关联到的对象,需要暂停用户线程。
并发标记:从GC Root开始对堆中的对象进行可达性分型,标记出存活的对象,用时比较久,可与用户线程并发执行。
重新标记:修正在并发标记阶段因用户线程运行发生改变的记录,需要暂停用户线程。
筛选回收:对各个Region的回收价值进行排序,根据用户所设置的回收时间制定回收计划,这个阶段可与用户线程并发执行。
如图所示:
;                  

G1目前是jdk9的默认垃圾收集器,一般在以下场景中,需要考虑是否需要使用G1垃圾收集器:
(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长

ZGC

   Z垃圾收集器(ZGC)是可伸缩的低延迟垃圾收集器。ZGC可以同时执行所有昂贵的工作,而不会将应用程序线程的执行停止超过10ms,这使得它适合于要求低延迟和/或使用非常大的堆(数TB)的应用程序。
   目前ZGC没有分代,每次GC都会标记整个堆,将堆分为 2M(small), 32M(medium), n*2M(large)三种大小的页面(Page)来管理,根据对象的大小来判断在哪种页面分配,大部分对象标记和对象转移都是可以和应用线程并发。只会在以下阶段会发生stop-the-world:

  1. GC开始时对root set的标记时

  2. 在标记结束的时候,由于并发的原因,需要确认所有对象已完成遍历,需要进行暂停

  3. 在relocate root-set 中的对象时

虽然ZGC属于最新的GC技术, 但是只在特定情况下具有绝对的优势, 如巨大的堆和极低的暂停需求。

   本篇文章只是对java的垃圾回收涉及到的方面作一个简单的概括,并没有涉及到具体的算法的分析以及垃圾收集器的内部实现原理。其旨在对java的垃圾回收机制有一个整体的了解,下一章将介绍垃圾收集器用到的一些参数来为GC日志的分析和调优作准备。

参考书籍

深入理解java虚拟机--周志明 著

Guess you like

Origin www.cnblogs.com/JackSparrow-/p/11624182.html