目录

新生代收集器

Serial收集器

ParNew收集器

Parallel Scavenge收集器

老年代收集器

Serial Old收集器

Parallel Old收集器

CMS收集器

Removed GC Options

G1收集器

Basic Concepts

Heap Layout

Garbage Collection Cycle


如果说收集算法内存回收方法论,那么垃圾收集器就是内存回收具体实现Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。这里讨论的收集器基于JDK 1.7 Update 14之后的HotSpot虚拟机(在这个版本中正式提供了商用的G1收集器,之前G1仍处于实验状态),这个虚拟机包含的所有收集器如图3-5所示。

(这两段我扫了一眼....balabala)

图3-5展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。接下来笔者将逐一介绍这些收集器的特性、基本原理和使用场景,并重点分析CMS和G1这两款相对复杂的收集器,了解它们的部分运作细节。

在介绍这些收集器各自的特性之前,我们先来明确一个观点:虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的收集器出现,更加没有万能的收集器,所以我们选择的只是对具体应用最合适的收集器。这点不需要多加解释就能证明:如果有一种放之四海皆准、任何场景下都适用的完美收集器存在,那HotSpot虚拟机就没必要实现那么多不同的收集器了。

自己分出来:

新生代收集器:Serial , ParNew , Parallel Scavenge

老年代收集器:Serial Old(MSC) , Parallel Old , CMS

新生代或老年代收集器:G1

后面书中介绍的顺序会是 新生代收集器 - > 老年代收集器 -> G1

可搭配使用

Serial :Serial Old,CMS

ParNew :Serial Old,CMS

Parallel Scavenge:Serial Old,Parallel Old

新生代/老年代 Serial Old Parallel Old CMS G1
Serial √  × √  ×
ParNew √  × √  ×
Parallel Scavenge √  √  × ×
G1 × × ×

各个收集器的优缺点:

新生代:

Serial 收集器:限定单个CPU的环境来说,可以获得最高的单线程收集效率

ParNew 收集器:多线程的并行收集器,优点是能和老年代的CMS搭配

Parallel Scavenge收集器:多线程的并行收集器,关注点是吞吐量,可控制的吞吐量,且提供 GC自适应的调节策略

老年代:

Serial Old 收集器:单线程收集器,使用“标记-整理”算法,在1.5及以下版本与Parallel Scavenge配合使用,或者当CMS收集器并发收集发生Concurrent Mode Failure时当备案

Parallel Old收集器:多线程的并行收集器,关注点是吞吐量

CMS收集器:关注停顿时间,并发收集,使用"标记-清除"算法,低停顿,缺点是1,CPU资源敏感,如果是单核机器不建议使用,两核以上才建议;2,浮动垃圾无法清除,会导致Concurrent Mode Failure;3,有大量的空间碎片

新生+老年都是G1:

G1收集器:关注停顿时间,并发收集,使用“标记-整理”算法,低停顿,将java堆划分为region,老年代和新生代概念上区分,物理上不区分,能预测的停顿,使用者可指定长度时间X毫秒段内消耗在垃圾收集的时间不超过m毫秒,缺点是:还没有大量的生产数据来为它做使用担保

关注停顿时间:停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验

关注吞吐量:而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

收集器使用的JVM参数:

ParNew:

指定它位新生代收集器:-XX:+UseConcMarkSweepGC 默认收集器就是它 或者 -XX:+UseParNewGC

限制GC线程数:-XX:ParallelGCThreads

Parallel Scavenge:

控制最大垃圾收集停顿时间:-XX:MaxGCPauseMillis

设置吞吐量大小:-XX:GCTimeRatio

GC自适应的调节策略开关:-XX:+UseAdaptiveSizePolicy

新生代收集器

Serial收集器

【文中没有讲,Serial收集器使用的GC算法复制算法

Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。大家看名字就会知道,这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU一条收集线程去完成垃圾收集工作更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程直到它收集结束。“Stop The World”这个名字也许听起来很酷,但这项工作实际上是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。读者不妨试想一下,要是你的计算机每运行一个小时就会暂停响应5分钟,你会有什么样的心情?图3-6示意了Serial/Serial Old收集器的运行过程。

写到这里,笔者似乎已经把Serial收集器描述成一个“老而无用、食之无味弃之可惜”的鸡肋了,但实际上到现在为止,它依然是虚拟机运行在Client模式下的默认新生代收集器。

它也有着优于其他收集器的地方简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。

ParNew收集器

ParNew收集器使用的GC算法复制算法且是并行收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew收集器的工作过程如图3-7所示。

(下面这三段...balabala看得看不下去就直接去看下面笔记总结)

ParNew收集器除了多线程收集之外,其他与Serial收集器相比并没有太多创新之处,但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器——CMS收集器(Concurrent Mark Sweep,本节稍后将详细介绍这款收集器),这款收集器是HotSpot虚拟机中第一款真正意义上的并发(Concurrent)收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作,用前面那个例子的话来说,就是做到了在你的妈妈打扫房间的时候你还能一边往地上扔纸屑。

不幸的是,CMS作为老年代的收集器,却无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作[1],所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。ParNew收集器也是使用-XX:+UseConcMarkSweepGC选项后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定它

ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收
集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。当然,随着可以使
用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。它默认开启的收集线程数与CPU的数量相
同,在CPU非常多(譬如32个,现在CPU动辄就4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的环境
下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数

笔记:说了这么多也只是说了几点:

1. ParNew收集器是Serial的多线程版本,很多代码是共用的

2. 它变成首选,不是因为有啥优势是因为CMS优秀:老年代CMS 只能和 新生代ParNew和 Serial搭配,现在大多数都多CPU所以都用ParNew

3. 两个CPU及以下的CPU情况,Serial依旧比ParNew优秀,因为Serial没有线程交互的开销

4. ParNew使用参数指定新生代收集器:-XX:+UseConcMarkSweepGC 默认收集器就是它 或者 -XX:+UseParNewGC

5. ParNew使用参数限制GC线程数:-XX:ParallelGCThreads

其中一段就在说可惜CMS不能和Parallel Scavenge配合

注意 从ParNew收集器开始,后面还会接触到几款并发和并行的收集器。在大家可能产生疑惑之前,有必要先解释两个名词:并发和并行。这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,它们可以解释如下。

●并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态

●并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行而垃圾收集程序运行于另一个CPU上

理解:

这边的概念是指用户线程和垃圾收集线程的并行和并发,不是单独指并行和并发的概念

并行和并发自己概念上的表述:【来源 Java并发编程原理与实战二:并行&并发&多线程的理解】

并发:

同一时间段有几个程序都处于已经启动到运行完毕之间,并且这几个程序都在同一个处理机(敲黑板!)上运行,并发的两种关系是同步和互斥;

(A程序和B程序都要同时在跑,但是一个跑道只能容得下一个程序,那么就A程序和B程序交替跑,A跑的时候教练说轮到B跑了,那么A就停下来,让B跑)

并行:

单处理器中,进程被交替执行,表现出一种并发的外部特征;(单处理器的时候,表现和并发一样)

多处理器中,由于进程可以交替执行还能重叠执行,实现并行处理,并行就是同时发生的多个并发事件,具有并发的含义,但并发不一定是并行,也就是说事件之间不一定要同一时刻发生(敲黑板!)

(有多个跑道,A程序一个跑道,B程序一个跑道,A程序先上路,B程序后上路,但AB都在跑,AB都在路上跑的这个时间段是并行)

括号是我的理解,可能理解有误,欢迎指点

Parallel Scavenge收集器

【Parallel Scavenge GC算法使用复制算法且是并行收集器】

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器……看上去和ParNew都一样,那它有什么特别之处呢?

Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间CPU总消耗时间比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

【看收集器这边有点脑子短路,写给自己看:

运行用户代码时间 + 垃圾收集时间 = 100,垃圾收集时间 = 1,吞吐量 = (100-1)/100 = 99% 】

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务

(介绍控制吞吐量的参数)
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是

控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数

以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

MaxGCPauseMillis参数允许的值是一个大于0毫秒数收集器将尽可能地保证内存回收花费的时间不超过设定值不过大家不要认为如果把这个参数的值设置得稍小一点就能使得系统的垃圾收集速度变得更快,GC停顿时间缩短是以牺牲 吞吐量  新生代空间 来换取的(敲黑板!)

系统把新生代调小一些,收集300MB新生代肯定比收集500MB快吧,这也直接导致垃圾收集发生得更频繁一些,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

【脑子短路的我再贴一下吞吐量的公式:吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

每10秒收集一次,每次GC 100毫秒,吞吐量 = 10/(10+0.1)=  99.0099%

每5秒收集一次、每次停顿70毫秒,吞吐量 = 5/(5+0.07) =  98.6193%

嗯,吞吐量下降了】

GCTimeRatio参数的值应当是一个大于0且小于100整数,也就是垃圾收集时间总时间比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认值为99,就是允许最大1%(即1/(1+99))的垃圾收集时间。

疑问:

等下?这倒数是怎么回事

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

倒数应该是 参数值=(运行用户代码时间+垃圾收集时间)/运行用户代码时间 

倒数 和 “垃圾收集时间占总时间的比率” 描述不符啊?

“垃圾收集时间占总时间的比率 ” 描述的公式应该是:参数值 = 垃圾收集时间/(运行用户代码时间+垃圾收集时间),设置19的话应该说明 垃圾收集时间总时间 的 19% 才对啊

按照书里后面的计算得到5%描述,这不是直接设置比值,是以 参数 = N,最大GC时间占比 = 1/(1+N)的公式来算啊


疑问解决:

网址:https://docs.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html

找了官方的说明,贴一下官方说明:

-XX:GCTimeRatio=nnn

A hint to the virtual machine that it's desirable that not more than 1 / (1 + nnn) of the application execution time be spent in the collector.

For example -XX:GCTimeRatio=19 sets a goal of 5% of the total time for GC and throughput goal of 95%. That is, the application should get 19 times as much time as the collector.

理解:

若参数设置的值为N,那么参数设置的值的意义是: 应用程序运行的时间收集器时间  的N 

应用程序运行时间也就是书里说的 运行用户代码时间收集器时间也就是垃圾收集时间

还是按照将参数设置为19来看,就是 运行用户代码时间 是 垃圾收集时间 的19倍

那么 设 垃圾收集时间 为1,运行用户代码 时间则为19

GC时间占比 =  垃圾收集时间 /(运行用户代码时间+垃圾收集时间) = 1/(1+19)= 5%

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间) = 19/(1+19)= 95%

那么就是希望达到 GC时间最大占比为5% 和 吞吐量最大为95%

由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。

除上述两个参数之外,Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得关注。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)

如果读者对于收集器运作原来不太了解,手工优化存在困难的时候,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或 GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

官方介绍:http://download.oracle.com/javase/1.5.0/docs/guide/vm/gc-ergonomics.html。

老年代收集器

Serial Old收集器

老年代的单线程收集器,使用“标记-整理”算法
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。

如果在Server模式下,那么它主要还有两大用途:

一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用[1]

【书作者的注释:需要说明一下,Parallel Scavenge收集器架构中本身有PS MarkSweep收集器来进行老年代收集,并非直接使用了Serial Old收集器,但是这个PS MarkSweep收集器与Serial Old的实现非常接近,所以在官方的许多资料中都是直接以Serial Old代替PS MarkSweep进行讲解,这里笔者也采用这种方式。】

另一种用途就是作为CMS收集器后备预案,在并发收集发生Concurrent Mode Failure时使用

这两点都将在后面的内容中详细讲解。Serial Old收集器的工作过程如图3-8所示。

Parallel Old收集器

Parallel Old收集器是多线程的并行收集器,使用“标记-整理”算法

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外别无选择(还记得上面说过Parallel Scavenge收集器无法与CMS收集器配合工作吗?)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。

直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。Parallel Old收集器的工作过程如图3-9所示。

CMS收集器

CMS收集器是复合收集器(单线程+并行多线程+并发多线程),基于“标记—清除”算法实现

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。

从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤

包括:
初始标记(CMS initial mark)【单线程】

并发标记(CMS concurrent mark)【并发多线程

重新标记(CMS remark)【并行多线程

并发清除(CMS concurrent sweep)【并发多线程

其中,初始标记重新标记这两个步骤仍然需要“Stop The World”

初始标记仅仅只是标记一下GC Roots能直接关联到的对象速度很快

并发标记阶段就是进行GC RootsTracing的过程

重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动那一部分对象标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短

理解:并发标记阶段是并发多线程,标记时用户线程也在跑,所以会导致并发标记了的对象会变动,例如本来是活的对象,并发标记过了他后,用户程序使这对象无GC-ROOT到达了,这时标记就产生变动了,重新标记则标记这对象为死对象。(个人还是觉得有点怪怪的,理解有误的话,请指出)

由于整个过程中耗时最长并发标记并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说CMS收集器的内存回收过程是与用户线程一起并发执行。通过图3-10可以比较清楚地看到CMS收集器的运作步骤中并发和需要停顿的时间。

理解:所以CMS是将耗时最长的两个阶段并发处理,使得停顿时间达到最小

CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集低停顿,Sun公司的一些官方文档中也称之为并发低停顿收集器(Concurrent Low Pause Collector)。但是CMS还远达不到完美的程度,它有以下3个明显的缺点

1.CMS收集器对CPU资源非常敏感

其实,面向并发设计的程序都对CPU资源比较敏感

在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢总吞吐量会降低

CMS默认启动的回收线程数(CPU数量+3)/4

也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降

但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。

理解&疑问:依照我的习惯又要在这停顿 (CPU数量+3)/4

4个以上先当4个吧,那就是 线程数 =(4+3)/4 = 1.75  == int 1, 1个线程

2个,线程数 = (2+3)/4 = 1.25 == int 1,1个线程

这里的说明是按照,一个回收线程完全占用一个CPU数量的资源?

所以4个CPU占用1/4的CPU做回收线程,占用25%的CPU资源

2个CPU占用1/2的CPU做回收线程,占用50%的CPU资源

其实我特别想知道作者说明这段的出处是哪里。。。这样就清楚我理解的对不对了,如理解有误,欢迎指正


由于对作者的默认启动回收线程数含有疑问,找到官方解释了,贴一下

地址:https://docs.oracle.com/en/java/javase/11/gctuning/mostly-concurrent-collectors.html#GUID-DFA8AF9C-F3BC-4F12-99CE-45AB6F22F15A

The mostly concurrent collector trades processor resources (which would otherwise be available to the application) for shorter major collection pause time.

The most visible overhead is the use of one or more processors during the concurrent parts of the collection. On an N processor system, the concurrent part of the collection uses K/N of the available processors, where 1 <= K <= ceiling{N/4}. In addition to the use of processors during concurrent phases, additional overhead is incurred to enable concurrency. Thus, while garbage collection pauses are typically much shorter with the concurrent collector, application throughput also tends to be slightly lower than with the other collectors.

On a machine with more than one processing core, processors are available for application threads during the concurrent part of the collection, so the concurrent garbage collector thread doesn't pause the application. This usually results in shorter pauses, but again fewer processor resources are available to the application and some slowdown should be expected, especially if the application uses all of the processing cores maximally. As N increases, the reduction in processor resources due to concurrent garbage collection becomes smaller, and the benefit from concurrent collection increases. See Concurrent Mode Failure, which discusses potential limits to such scaling.

Because at least one processor is used for garbage collection during the concurrent phases, the concurrent collectors don't normally provide any benefit on a uniprocessor (single-core) machine.

理解:【

第二段的那句:在N个处理系统中,并发收集部分使用 K/N的可使用处理器, 1<= K <= ceiling{N/4} (取N/4的上限)

N = 16,ceiling{N/4}  = ceiling{16/4} = 4,  1/16 <= K/16 <= 4/16,占用 大于或等于6.25%,小于或等于25% 的处理器资源

N = 8,ceiling{N/4}  = ceiling{8/4} = 2,  1/8 <= K/8 <= 2/8,占用 大于或等于12.5%,小于或等于25% 的处理器资源

N = 4,ceiling{N/4}  = ceiling{4/4} = 1,  1/4 <= K/4 <= 1/4,占用25%的处理器资源

N = 2,ceiling{N/4}  = = ceiling{2/4} =1 那么就是 1/2 <= K/2 <=  1/2 ,占用50%的处理器资源

N = 1,ceiling{N/4}  = = ceiling{1/4} =1 那么就是 1/1 <= K/1 <=  1/1 ,占用100%的处理器资源

(我数学过关了吗。)

最后一段那句:由于 至少一个处理器 会被垃圾收集中的并发收集阶段使用,并发收集器对单核机器没有提供任何帮助

我还是不太理解作者理解的CPU数量和线程数量之间的关系,我还是直接理解 垃圾收集器并发收集占用CPU资源百分比好了。。。。

(下面这段是了解用的,因为这东西被声明不提倡使用了,怕我脑子混了,我选择忘记它)

为了应付这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想一样,就是在并发标记、清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,也就是速度下降没有那么明显。实践证明,增量时的CMS收集器效果很一般,在目前版本中,i-CMS已经被声明为“deprecated”,即不再提倡用户使用。

2.CMS收集器无法处理浮动垃圾(Floating Garbage)可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生

浮动垃圾(Floating Garbage)的解释:

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC再清理。这一部分垃圾就称为“浮动垃圾”

(触发条件)

也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集需要预留一部分空间提供并发收集时的程序运作使用

JDK 1.5的默认设置下,CMS收集器老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,

JDK1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了

所以说参数-XX:CM SInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低

3.收集结束时会有大量空间碎片产生。

还有最后一个缺点,在本节开头说过,CMS是一款基于“标记—清除”算法实现的收集器,如果读者对前面这种算法介绍还有印象的话,就可能想到这意味着收集结束时会有大量空间碎片产生

空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象不得不提前触发一次Full GC

为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程内存整理的过程无法并发空间碎片问题没有了,但停顿时间不得不变长

虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的Full GC默认值为0,表示每次进入Full GC时都进行碎片整理)。

理解:

压缩就是指的碎片整理

Full GC: A full heap garbage collection (Full GC)  全堆GC (新生代 + 老年代 GC)

不小心查了下,官方在java11把这两参数给删除了。。。

官方地址:https://docs.oracle.com/en/java/javase/11/migrate/index.html#JSMIG-GUID-1F270BDA-50B0-49C8-807E-0B727CCC5169

Removed GC Options

The following GC combinations will cause your application to fail to start in JDK 9 and later releases:

  • DefNew + CMS
  • ParNew + SerialOld
  • Incremental CMS

The foreground mode for CMS has also been removed. The command-line flags that were removed are -Xincgc-XX:+CMSIncrementalMode, -XX:+UseCMSCompactAtFullCollection-XX:+CMSFullGCsBeforeCompaction, and -XX:+UseCMSCollectionPassing.

The command-line flag -XX:+UseParNewGC no longer has an effect. The ParNew flag can be used only with CMS and CMS requires ParNew. Thus, the -XX:+UseParNewGC flag has been deprecated and is eligible for removal in a future release.

G1收集器

G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点

并行与并发G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。

可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合

(G1的回收计划)

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

G1把内存“化整为零”的思路,理解起来似乎很容易,但其中的实现细节却远远没有想象中那样简单,否则也不会从2004年Sun实验室发表第一篇G1的论文开始直到今天(将近10年时间)才开发出G1的商用版。

笔者以一个细节为例:把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?听起来顺理成章,再仔细想想就很容易发现问题所在Region不可能是孤立的。

一个对象分配在某个Region中,它并非只能被本Region中的其他对象引用,而是可以与整个Java堆任意的对象发生引用关系。那在做可达性判定确定对象是否存活的时候,岂不是还得扫描整个Java堆才能保证准确性?

这个问题其实并非在G1中才有,只是在G1中更加突出而已。在以前的分代收集中,新生代的规模一般都比老年代要小许多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象也面临相同的问题,如果回收新生代时也不得不同时扫描老年代的话,那么Minor GC的效率可能下降不少。

解决问题:
G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set避免全堆扫描。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作会产生一个Write Barrier暂时中断写操作检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象)如果是便通过CardTable把相关引用信息记录到被引用对象所属RegionRemembered Set之中。当进行内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

【个人理解:引用类型向下添加引用时,避免全堆扫描;可达性分析是从上到下来查找,root引用B,则B可达是活的,问题是如果B和root不在一个群,那么还要扫描其他的群,效率就会低下,关键是如何直接定位,B上面有root,而不进行全扫描

Reference类型的数据A即将要加一个引用对象B,虚拟机发现了,暂时中断这个写操作,检查这个即将要引用的对象B是否和A处在不同的Region,如果在不同的Region,就在对象B的Region中把相关的引用信息记录到它(B)的Remembered Set中,进行内存回收的时候,GC根节点枚举范围加入Remembered Set,对于这个例子,也就是加入了B被另一个Region的A引用的信息

对于分代来说,新生代朝生夕死,影响它效率的因素,如果老年代root引用新生代B,则这个新生代B可达是活的,那么可达性分析扫描就还要扫老年代,同上,定位新生代B上有老年代,而不进行老年代扫描

老年代的Reference类型的数据A即将要加一个引用对象B,虚拟机发现了,暂时中断这个写操作,检查这个即将要引用的对象B是否是新生代中的对象,如果是新生代中的对象,就在新生代中把相关的引用信息记录到新生代的Remembered Set中,进行内存回收的时候,GC根节点枚举范围加入Remembered Set,对于这个例子,也就是加入了新生代对象B被老年代A引用的信息

如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:

初始标记(Initial Marking) 【STW

并发标记(Concurrent Marking)【并发

最终标记(Final Marking)【STW

筛选回收(Live Data Counting and Evacuation)【STW

对CMS收集器运作过程熟悉的读者,一定已经发现G1的前几个步骤的运作过程和CMS有很多相似之处。

初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。

并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行

最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。(???)

最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,从Sun公司透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行(???),但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。通过图3-11可以比较清楚地看到G1收集器的运作步骤中并发和需要停顿的阶段。

【理解:

按照官方的说明,是最终标记和筛选回收是stw的,在最终标记和筛选回收之间还有一个并发的过程,这个过程用来计算回收价值和成本的,最终标记和筛选回收确实是STW

在官方java9的jvm G1说明:https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm#JSGCT-GUID-F1BE86FA-3EDC-4D4F-BDB4-4B044AD83180

找到一篇讲G1不错的传送门:https://blog.csdn.net/coderlius/article/details/79272773

Basic Concepts

G1 is a generational, incremental, parallel, mostly concurrent, stop-the-world, and evacuating garbage collector which monitors pause-time goals in each of the stop-the-world pauses. Similar to other collectors, G1 splits the heap into (virtual) young and old generations. Space-reclamation efforts concentrate on the young generation where it is most efficient to do so, with occasional space-reclamation in the old generation

Some operations are always performed in stop-the-world pauses to improve throughput. Other operations that would take more time with the application stopped such as whole-heap operations like global marking are performed in parallel and concurrently with the application. To keep stop-the-world pauses short for space-reclamation, G1 performs space-reclamation incrementally in steps and in parallel. G1 achieves predictability by tracking information about previous application behavior and garbage collection pauses to build a model of the associated costs. It uses this information to size the work done in the pauses. For example, G1 reclaims space in the most efficient areas first (that is the areas that are mostly filled with garbage, therefore the name).

G1 reclaims space mostly by using evacuation: live objects found within selected memory areas to collect are copied into new memory areas, compacting them in the process. After an evacuation has been completed, the space previously occupied by live objects is reused for allocation by the application.

【G1回收空间通常使用evacuation:在选定的内存区域内找到要收集的活对象,将其复制到新的内存区域,并在此过程中对其进行压缩。evacuation完成后,应用程序将重用以前由活对象占用的空间进行分配。】

The Garbage-First collector is not a real-time collector. It tries to meet set pause-time targets with high probability over a longer time, but not always with absolute certainty for a given pause.

Heap Layout

G1 partitions the heap into a set of equally sized heap regions, each a contiguous range of virtual memory as shown in Figure 9-1. A region is the unit of memory allocation and memory reclamation. At any given time, each of these regions can be empty (light gray), or assigned to a particular generation, young or old. As requests for memory comes in, the memory manager hands out free regions. The memory manager assigns them to a generation and then returns them to the application as free space into which it can allocate itself.

Figure 9-1 G1 Garbage Collector Heap Layout


Description of "Figure 9-1 G1 Garbage Collector Heap Layout "

The young generation contains eden regions (red) and survivor regions (red with "S"). These regions provide the same function as the respective contiguous spaces in other collectors, with the difference that in G1 these regions are typically laid out in a noncontiguous pattern in memory. Old regions (light blue) make up the old generation. Old generation regions may be humongous (light blue with "H") for objects that span multiple regions.

An application always allocates into a young generation, that is, eden regions, with the exception of humongous, objects that are directly allocated as belonging to the old generation.【还是和之前一样,大对象直接进老年代】

G1 garbage collection pauses can reclaim space in the young generation as a whole, and any additional set of old generation regions at any collection pause. During the pause G1 copies objects from this collection set to one or more different regions in the heap. The destination region for an object depends on the source region of that object: the entire young generation is copied into either survivor or old regions, and objects from old regions to other, different old regions using aging.

Garbage Collection Cycle

On a high level, the G1 collector alternates between two phases. The young-only phase contains garbage collections that fill up the currently available memory with objects in the old generation gradually. The space-reclamation phase is where G1 reclaims space in the old generation incrementally, in addition to handling the young generation. Then the cycle restarts with a young-only phase.

划重点:G1收集器在两个阶段转换,young-only阶段,包含垃圾收集,它是用老年代的对象逐渐填充当前可用的内存。space-reclamation 阶段,除了回收年轻代,G1还逐步回收老年代的空间。】

Figure 9-2 gives an overview about this cycle with an example of the sequence of garbage collection pauses that could occur:

Figure 9-2 Garbage Collection Cycle Overview

Description of "Figure 9-2 Garbage Collection Cycle Overview "

The following list describes the phases, their pauses and the transition between the phases of the G1 garbage collection cycle in detail:

  1. Young-only phase: This phase starts with a few young-only collections that promote objects into the old generation. The transition between the young-only phase and the space-reclamation phase starts when the old generation occupancy reaches a certain threshold(临界值), the Initiating Heap Occupancy threshold. At this time, G1 schedules an Initial Mark young-only collection instead of a regular young-only collection.【?求解释啊

    • Initial Mark : This type of collection starts the marking process in addition to performing a regular young-only collection. Concurrent marking determines all currently reachable (live) objects in the old generation regions to be kept for the following space-reclamation phase. While marking hasn’t completely finished, regular young collections may occur. Marking finishes with two special stop-the-world pauses: Remark and Cleanup.

    • Remark: This pause finalizes the marking itself, and performs global reference processing and class unloading. Between Remark and Cleanup G1 calculates a summary of the liveness information concurrently, which will be finalized and used in the Cleanup pause to update internal data structures.【划重点!

    • Cleanup: This pause also reclaims completely empty regions, and determines whether a space-reclamation phase will actually follow. If a space-reclamation phase follows, the young-only phase completes with a single young-only collection.【意思是,除了清理垃圾外,还清理了空区域,并确定是否确实需要进入space-reclamation阶段?不理解啊,那这样stw时间不是更长啦?

  2. Space-reclamation phase: This phase consists of multiple mixed collections that in addition to young generation regions, also evacuate live objects of sets of old generation regions. The space-reclamation phase ends when G1 determines that evacuating more old generation regions wouldn't yield enough free space worth the effort.

After space-reclamation, the collection cycle restarts with another young-only phase. As backup, if the application runs out of memory while gathering liveness information, G1 performs an in-place stop-the-world full heap compaction (Full GC) like other collectors.

【中间一段是测试G1】

在更大规模的生产环境下,笔者引用一段在StackOverflow.com上看到的经验与读者分享:“我在一个真实的、较大规模的应用程序中使用过G1:大约分配有60~70GB内存,存活对象大约在20~50GB之间。服务器运行Linux操作系统,JDK版本为6u22。G1与PS/PS Old相比,最大的好处是停顿时间更加可控、可预测,如果我在PS中设置一个很低的最大允许GC时间,譬如期望50毫秒内完成GC(-XX:MaxGCPauseMillis=50),但在65GB的Java堆下有可能得到的直接结果是一次长达30秒至2分钟的漫长的Stop-The-World过程;而G1与CMS相比,虽然它们都立足于低停顿时间,CMS仍然是我现在的选择,但是随着Oracle对G1的持续改进,我相信G1会是最终的胜利者。如果你现在采用的收集器没有出现问题,那就没有任何理由现在去选择G1,如果你的应用追求低停顿,那G1现在已经可以作为一个可尝试的选择,如果你的应用追求吞吐量,那G1并不会为你带来什么特别的好处”。

JVM 垃圾收集器 学习笔记(《深入理解java虚拟机》之六 垃圾收集)相关推荐

  1. 深入理解Java虚拟机 -- 经典垃圾收集器

    文章目录 1. 综述 2. Serial收集器 3. ParNew收集器 4. Parallel Scavenge收集器 5. Serial Old收集器 6. Parallel Old收集器 7. ...

  2. java虚拟机手动内存分配_《深入理解java虚拟机》-垃圾收集器与内存分配策略

    如何判断对象已死? 引用计数算法 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1:当引用失效时,计数器减1:其中计数器为0的对象是不可能再被使用的已死对象. 引用计数算法的实现很简单 ...

  3. 《深入理解Java虚拟机》——垃圾收集器的具体实现

    2019独角兽企业重金招聘Python工程师标准>>> 如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现.Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定 ...

  4. JVM学习笔记0:Java虚拟机概述

    目录 第1章 Java虚拟机概述 1.1 虚拟机与Java虚拟机 1.2 JVM 1.2.1 JVM的位置 1.2.2 JVM的整体结构 1.2.3 Java代码执行流程 1.2.4 JVM的架构模型 ...

  5. jvm垃圾回收器(《深入理解java虚拟机》)

    ---恢复内容开始--- 程序计数器.虚拟机栈.本地方法栈,这三个区域随着线程生,随着线程死.重复入栈出栈,还有基本确定 编译后 各区域所需要的内存大小是确定的.所以这部分回收不需要过多考虑. 主要回 ...

  6. [转][读书笔记]深入理解java虚拟机

    原文地址:http://blog.csdn.net/hanekawa/article/details/51972259 第二章 Java内存区域与内存溢出异常 一,运行时数据区域: 1.        ...

  7. java 上下文加载器_【深入理解Java虚拟机 】线程的上下文类加载器

    线程上下文类加载器 线程上下文类加载器( Thread Context ClassLoader) 是从JDK1.2 引入的,类Thread 的getContextClassLoader() 与 set ...

  8. 读书笔记-深入理解Java虚拟机

  9. 《深入理解Java虚拟机》阅读——垃圾回收机制

    <深入理解Java虚拟机>阅读--垃圾回收机制 前言 why--为什么需要垃圾回收 what--垃圾回收做些什么 where--去哪里回收垃圾 how--垃圾回收是怎么做的 垃圾是否要回收 ...

最新文章

  1. 介绍几种SSIS部署方式
  2. mysql master host_mysql异步备份的有关问题,MASTER_HOST可否指定多个
  3. Java8 函数式对齐约定————Eclipse自定义代码风格
  4. 6款英雄主题fcpx标题字幕插件:Hero Shot Titles for Mac
  5. 牛X | 一款比传统数据库快100-1000倍的数据库,认识一下
  6. 用python程序化交易名单_python程序化交易神器——tushare
  7. TensorFlow学习记录1-一些比较好的学习资源
  8. [2019杭电多校第五场][hdu6630]permutation 2
  9. CrystalReports水晶报表中的组
  10. smart原则_如何利用SMART原则制定适合自己的高效学习计划
  11. 网络数据包的封包格式
  12. 参与流片是一种怎样的体验
  13. 树莓派笔记17: 语音机器人
  14. WIN2016远程桌面服务配置和授权激活
  15. android 6g 有必要吗,Android手机6GB内存有必要吗?实测出真知
  16. 几分钟黑掉阿里,被马云500万年薪收编的黑客,现在混得咋样了?
  17. 好用的在线二维码生成器网站PHP源码
  18. Latex排版技巧汇总
  19. Mybatis-Plus多表关联查询的使用
  20. nutch java_Nutch:用Java调用,而不是命令行?

热门文章

  1. Mirror for Samsung TV for mac(三星智能电视投屏软件)
  2. PHP学习笔记好(待完善)
  3. 企业经营核心要素框架
  4. 在电脑上怎么做判断题打√或x_电脑基础知识竞赛试题
  5. Spring AOP tx:advice
  6. Python读取Execl文件批量生成二维码
  7. Apache Curator操作zookeeper的API使用
  8. 在IDEA中集成Jenkins插件 解决Missing or bad crumb data异常
  9. pocketsphinx 模型库_PocketSphinx声学模型的训练与使用
  10. Readme文件写法