Bootstrap

【JVM】GC

GC(Garbage Collection)

概述

        jvm 中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理。

        因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。

        当 JVM 进行垃圾回收时,无论内存是否充足,都会回收只被弱引用关联的对象。

简述Minor GC

        Minor GC指发生在新生代(Eden区)的垃圾收集,因为 Java 对象大多存活时间短,所以 Minor GC 非常频繁,一般回收速度也比较快。

简述Full GC

        Full GC 是清理整个堆空间—包括年轻代和永久代。调用System.gc(),老年代空间不足,空间分配担保失败,永生代空间不足会产生full gc。

对象存活(是否为垃圾)判断方法:

1.引用计数算法

        每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。

        此方法简单,无法解决对象相互循环引用的问题。一般不使用这种方法。

        因此在Java中并没有采用这种方式(Python采用的是引用计数法),而采用可达性分析算法。

2.可达性分析算法(Reachability Analysis):

        从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

在Java语言中,GC Roots包括:

虚拟机栈中引用的对象。(引用栈帧中的本地变量表的所有对象)

方法区中类静态属性实体引用的对象。(引用方法区中静态属性的所有对象)。

方法区中常量引用的对象。(引用方法区中常量的所有对象)

本地方法栈中JNI(Java Native Interface,Java本地接口)引用的对象。(引用Native方法的所有对象)

注意:被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

垃圾收集算法

  1. 标记-清除(Mark-Sweep)算法:
    1. 之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。
    2. 如首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
    3. 它的主要缺点有两个:
      • 一个是效率问题,标记和清除过程的效率都不高;
      • 另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    4. 优点
      最大的优点是,标记—清除算法中每个活着的对象的引用只需要找到一个即可,找到一个就可以判断它为活的。此外,更重要的是,这个算法并不移动对象的位置。
  2. 标记-压缩(整理)算法:
    1. 根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存(清理存活对象以外的内存)。
    2. 优点
      该算法不会像标记-清除算法那样产生大量的碎片空间。
    3. 缺点
      如果存活的对象过多,整理阶段将会执行较多复制操作,导致算法效率降低。
  3. 复制算法:
    1. 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    2. 这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
    3. 只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象(存活率较高的对象)则导致效率降低。
    4. 复制算法是要复制存活对象到另一块区域,所以在根可达算法发现存活对象后是直接复制到另一块区域,即在根可达分析过程中就已经完成了筛选(复制),待复制完成后,直接清理掉另一块区域即可,所以没有标记的必要。
    5. 优点

      实现简单;不产生内存碎片。

    6. 缺点

      ​​每次运行,总有一半内存是空的,导致可使用的内存空间只有原来的一半。
  4. 分代收集算法:
    1. GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
    2. “分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。

垃圾收集器

概述:

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。

常用的收集器组合(新生代+老年代):

新生代和老年代根据需求使用不同的垃圾回收器组合。

垃圾收集器的选择

        JVM中,青年代和老年代特点迥异,青年代中对象“朝生夕死”的特点,回收频率较高,适合采用复制算法;而老年代则更适合“标记-整理”算法。鉴于此,JVM采用分代回收的策略:青年代采用复制算法的回收器,老年代采用“标记-整理”算法的回收器。

分类:

Serial收集器

单线程串行收集器;

简单高效、稳定;

新生代使用标记复制算法,老年代使用标记整理(压缩)算法;

垃圾回收的时候,必须暂停其他所有线程;

可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收(单个gc垃圾回收线程)。

垃圾收集的过程中会Stop The World(整个服务暂停),也就是运行垃圾回收线程的时候,所有的用户线程都需要停止(大概几百毫秒)。

参数控制: -XX:+UseSerialGC 串行收集器

为什么需要stop the world:
可达性分析算法中,枚举根节点(GC ROOT)会导致所有java程序执行线程停顿。
分析工作必须在一个能确保一致性的快照中进行
一致性指整个分析期间整个执行系统像被冻结在某个时间点上
如果出现分析过程中对象引用关系还在不断地变化,则分析结果的准确性无法保证。

举个例子,如果分析时判断该对象已经没有引用,而分析后下一秒该对象又有了引用,则gc时会将该对象清理,导致错误发生。

ParNew收集器

Serial收集器的多线程版本。

新生代并行(多个gc垃圾回收线程),老年代串行(单个gc垃圾回收线程)。
新生代复制算法、老年代标记-压缩算法。
垃圾收集的过程中仍会Stop The World(整个服务暂停)。
参数控制:
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量

Parallel收集器

Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;
也可以通过参数控制GC的时间不大于多少毫秒或者比例;
新生代复制算法、老年代标记-压缩;
垃圾收集的过程中仍会Stop The World(整个服务暂停);
参数控制:
-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行;

Parallel Scavenge收集器

注重吞吐量,即cpu运行代码时间/cpu耗时总时间(cpu运行代码时间+ 垃圾回收时间)。
新生代使用复制算法,老年代使用标记整理算法。
参数控制:
-XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间,大于0的毫秒数
-XX:GCTimeRatio 设置垃圾收集时间占总时间的比率,0<n<100的整数​​​​​​​

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供。
参数控制:
-XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

CMS收集器

CMS垃圾收集器为最早提出的并发收集器,垃圾收集线程与用户线程同时工作。

采用标记清除算法。该收集器分为初始标记、并发标记、并发预清理、并发清除、并发重置这么几个步骤。

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。

注重最短时间停顿。
目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
收集过程:
初始标记(CMS initial mark):暂停其他线程(stop the world),标记与GC roots直接关联的对象。并发标记:可达性分析过程(程序不会停顿)。
并发标记(CMS concurrent mark):查找执行并发标记阶段从年轻代晋升到老年代的对象,重新标记,暂停虚拟机(stop the world)扫描CMS堆中剩余对象。
重新标记(并发重置)(CMS remark):重置CMS收集器的数据结构。
并发清除(CMS concurrent sweep):清理垃圾对象,(程序不会停顿)。

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew)

优点: 并发收集、低停顿
缺点: 产生大量空间碎片、并发阶段会降低吞吐量
参数控制:
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)

G1收集器

和之前收集器不同,该垃圾收集器把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。

初始标记:标记与GC roots直接关联的对象。

并发标记:可达性分析。

最终标记,对并发标记过程中,用户线程修改的对象再次标记一下。

筛选回收:对各个Region的回收价值和成本进行排序,然后根据用户所期望的GC停顿时间制定回收计划并回收。

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:
空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。
收集步骤:
标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。

GC触发

(1)程序调用System.gc方法时可以触发;
(2)系统自身来决定GC触发的时机(根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)
GC又分为 minor GC 和 Full GC (也称为 Major GC )
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
  a.调用System.gc时,系统建议执行Full GC,但是不必然执行;
  b.老年代空间不足;
  c.方法去空间不足;
  d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存;
  e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;

;