JVM——垃圾回收器

JVM——垃圾回收器

  • 按照工作模式分,可以分为并发式垃圾回收器独占式垃圾回收器
  • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间
  • 独占式垃圾回收器(stop the world)一旦运行,就停止应用程序中的所有用
    户线程,直到垃圾回收过程完全结束
    JVM——垃圾回收器_第1张图片
  • 按碎片处理方式分,可分为压缩式垃圾回收器非压缩式垃圾回收器
    压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理
    消除回收后的碎片非压缩式的垃圾回收器不进行这步操作。
  • 按工作的内存区间分,又可分为年轻代垃圾回收器老年代垃圾回收器

垃圾回收器(7大经典)

串行回收器: serial、serial old
并行回收器: ParNew、Parallel scavenge、Parallel old
并发回收器:CMS、G1

收集器与垃圾分代的关系

JVM——垃圾回收器_第2张图片
JVM——垃圾回收器_第3张图片

serial -》 serial old
parNaw -》CMS GC -》(备用)serial old
parallel GC -》parallel
old GC G1 -》 G1
根据场景使用最使用的收集器。

Serial回收器(串行回收)

JVM——垃圾回收器_第4张图片

总结:
这种垃圾收集器大家了解,现在已经不用串行的了。而且在限定单核cpu才可以用。现在都不是单核的了。
对于交互较强的应用而言,这种垃圾收集器是不能接受的。一般在Javaweb应用程序中是不会采用串行垃圾收集器的。

ParNew回收器(并行回收)

Par是Parallel的缩写,New:只能处理的是新生代
JVM——垃圾回收器_第5张图片
对于新生代,垃圾回收次数较频繁,我们可以使用并行,来使垃圾回收和应用线程交替执行,减少STW暂停时间。
对于老年代,垃圾回收次数较少,我们使用串行的方式,来减少线程切换消耗的资源。

Parallel回收器(吞吐量优先)

和ParNew收集器不同,Parallel scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
自适应调节策略也是Parallel scavenge与ParNew一个重要区别。
JVM——垃圾回收器_第6张图片
如图所示新生代采用的也是复制算法,同样用的是并行的方式,来减少STW暂停时间
老年代采用的是标记-压缩算法,与parNew不同的是采用的是并行的方式

总结:

在程序吞吐量优先的应用场景中,Parallel收集器和Parallel old收集器的组合,在server模式下的内存回收性能很不错。 在Java8中,默认是此垃圾收集器。

CMS回收器(低延迟)

CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间
CMS的垃圾收集算法采用标记-清除算法,并且也会"stop-the-world"

JVM——垃圾回收器_第7张图片

  • 第一阶段初始标记(串行): 此阶段会标记GC Roots直接关联的对象,但是在此操作的期间会触发STW,但这个过程也是极快的
  • 第二阶段并发标记(并行): 此阶段会从GC Roots直接关联的对象开始遍历整个对象树,对其标记,这个过程是并行进行的,用户线程和垃圾回收是同时进行,不会出现暂停。
  • 第三阶段重新标记(并行): 因为之前的并发标记是并行进行的,用户可能在标记的过程中又使对象要进行回收了,此阶段就是为了重新标记这些产生变动的对象
  • 第四阶段并发清除(并行): 此阶段会将标记的对象进行清除,释放内存空间,此阶段也不会出现暂停。

因为CMS是使用标记-清除算法,我们要额外使用一个空闲列表来维护空间内存。
那为什么不用标记-整理而是用标记-清除呢?

因为CMS在清除数据的时候是并发清除,这时候用户线程还能使用活数据,如果这时候
我们改动内存的引用地址,用户线程就找不到之前的引用了,所以我们只能使用标记-清除,在改动活数据的情况下清除不可达对象。

CMS的优缺点
优点:并发收集、低延迟
缺点:
1)会产生内存碎片
2)无法处理浮动垃圾:因为我们标记是并发标记,那么用户线程可能会在这个阶段产生新垃圾对象,CMS无法对这些垃圾对象进行标记,会导致这些新垃圾对象没有被及时回收,只能等待下一次GC回收。

G1回收器(区域化分代)

因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden、幸存者o区,幸存者1区,老年代等。
G1 Gc有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Regiono由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First) 。JVM——垃圾回收器_第8张图片
为什么要设置H:
我们向堆中放大对象,一般放在老年代中,但是如果是短期存在的大对象,我们就会用一个H区来存放,如果这个对象一个H区不够放,就用连续的H区来存放,为了能找到连续的H区,有时候不得不启动Full Gc。G1的大多数行为都把H区作为老年代的一部分来看待。
s,.

使用的算法:
内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact算法,两种算法都可以避免内存碎片。

可预测的停顿时间模型(即:软实时soft real-time)

G1 跟踪各个 Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
JVM——垃圾回收器_第9张图片

JVM——垃圾回收器_第10张图片
JVM——垃圾回收器_第11张图片

垃圾回收的三个环节

  • 年轻代GC
  • 老年代并发标记过程
  • 混合回收
    JVM——垃圾回收器_第12张图片
  • 当年轻代的Eden区用尽时开始年轻代回收过程,G1的年轻代收集阶段是一个并行的独占式收集器
  • 堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程
  • 标记完成马上开始混合回收过程。对于一个混合回收期,G1 Gc从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收-小部分老年代的Region就可以了

一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?

解决方法:

  • Remembered set来避免全局扫描:每个Region都有一个对应的Remembered set;
  • Reference类型数据写操作时,都会产生一个write Barrier暂时中断操作;
  • 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象);
  • 通过cardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered set中;
  • 在GC根节点的枚举范围加入Remembered Set;就可以保证不进行全局扫描,也不会有遗漏。
    JVM——垃圾回收器_第13张图片

回收过程一(年轻代GC):

当Eden区满时才会触发年轻代垃圾回收,年轻代垃圾回收只会回收Eden区和Survivor区

首先G1停止应用程序的执行(Stop-The-World)G1创建回收集(collection set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和survivor区所有的内存分段。

JVM——垃圾回收器_第14张图片
JVM——垃圾回收器_第15张图片
脏卡表队列作用:

Reset更新需要线程同步,所以开销会很大,因此不能实时更新,因此我们需要把引用对象被其他对象引用的关系放在一个脏卡表队列中,当年轻代回收的时候会进行STW,所以我们也正好把脏卡表队列中的值更新到Rset中,这样不仅没有涉及到开销问题,还可以保证Rset中的数据是准确的.

G1回收过程二:并发标记过程

JVM——垃圾回收器_第16张图片

G1回收过程二:混合回收

JVM——垃圾回收器_第17张图片
JVM——垃圾回收器_第18张图片

G1回收可选的过程四:Full Gc

JVM——垃圾回收器_第19张图片

总结:

JVM——垃圾回收器_第20张图片

你可能感兴趣的:(JVM,java,程序人生,压力测试)