详细的图又需要的,评论区留言,后面发给你
上回正说着,保洁队长和他的几个组因为太饿,给跑路了,给打手都快气坏了,还没打扫好就撒腿跑了。
之前已经提到过下面几个点
- 打手有的地可以自己打扫(比如线程私有的本地方法栈等,就不需要垃圾回收器帮咱们来处理垃圾),不需要别人帮咱们来打扫。点点有惊喜
- 打手们共享的,比如拳台呀、小胡和敏小言的民政&局故事(指的是堆和方法区)需要专人来帮忙打扫。
现在还有几个问题就是:
- 保洁组长手底下那几个组到底是谁打扫哪里呀?
- 用啥工具打扫呀?
相信不仅我作为读者有这个疑问,打手估计都快愁死了!!!
快到大年三十了,打手和保洁组长都急了,所以保洁组长也很迅速的带着人手来了。而且保洁队还托着几个大块头来了。
打手急了:哎哎哎,啊,你们轻点,我还有俩客人,小胡和敏小言还坐那准备看我的比赛呢,况且你们这大块头别给我地震坏了。
保洁组张连忙说,没事,我们这几个都很安全而且效率很高,别怕。
打手:你们这拉的都是啥呀。
保洁组长:忘了说了,我们打扫卫生并不是按老一套那种每个组带着不同数量的扫帚和拖把等工具去到自己不同的地盘不停扫扫拖拖。我们这个大块头总共分七个种类,他们之间一些也可以联合使用(连线的代表两个收集器可以搭配使用)。
收集器没有谁比谁好,我们选择的是对具体应用最合适的收集器
打手:你这啥呀,我都看不清
保洁:我这只是个大概的展示图,下面是具体的细节,别着急呀,且听我慢慢道来。
打手:好好好,看你一会咋介绍你这几个大宝贝
打手,对了,就算你不用普通的工具,你地盘也还是得划分一下吧
保洁组长:当然喽,工具和地盘缺一不可。我们地盘是这样划分的,算法,不和你叨叨叨了,给你看看明明白白的图。
打手,现在就可以给你介绍一下我们带来的大块头了。
我们大块头按照存在位置是这样分类的
位于新生代中的
,(我们这批大块头相对来说是比较新的,新生代里面嘛),下面这几个- Serial:串行收集器。
单线程的收集器
- 单线程有两层含义:
- 只会使用一条垃圾收集线程去完成垃圾收集工作
- 收集垃圾时,必须stop the world【STW,
A在进行垃圾收集工作的时候必须暂停其他所有的工作线程直到A收集结束。(虚拟机在后台自动发起和完成,用户不可见的情况下把用户正常工作的线程全部停掉)
】,使用复制算法负责新生代的垃圾回收工作
,可以与CMS垃圾回收器一起搭配工作。
新生代采用标记-复制算法,老年代采用标记-整理算法
。- 它的最大特点是
简单高效
(单个CPU环境时没有线程交互的开销可以专心做垃圾收集,自然可以获得最高的单线程收集效率)。Serial效率会比较慢。但是Serial确是所有垃圾回收器里面消耗额外内存最小的
,没错,就是因为简单。- 在进行垃圾回收时,需要对所有正在执行的线程暂停(stop the world),(相当于咱们计算机每运行一个小时就得暂停响应五分钟,忙的时候肯定不方便),对于有些应用是难以接受的,但是如果应用的实时性要求不是那么高,只要停顿的时间控制在N毫秒之内,大多数应用还是可以接受的。实际上到现在,虽然缺点明显,但是
Serial依然是虚拟机运行在Client模式下的默认新生代收集器
- 单线程有两层含义:
- ParNew:
Serial收集器的多线程版本【除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。】
,也需要stop the world,采用复制算法负责新生代的垃圾回收工作
,可以与CMS垃圾回收器一起搭配工作。
- 使用多线程进行垃圾收集
- 是许多运行在Server模式下的虚拟机首选的新生代收集器(一个很重要的原因是
除了Serial收集器外目前只有ParNew能与CMS收集器配合使用
) - 其余的行为与Serial收集器完全一样(在实现上这两种收集器也共用了很多代码),比如:
- Serial收集器可用的所有控制参数(比如:-XX:SurvivorRatio、-XX:PretenureThreshord、-XX:HandlePromotionFailure)
- 收集算法
- Stop The World
- 对象分配规则
- 回收策略等
- Parallel Scavenge:吞吐量优先的收集器
新生代收集器,使用复制算法的收集器
,并发的多线程收集器
,目标是达到一个可控的吞吐量(吞吐量指的是CPU用于运行用户代码的时间和CPU总消耗时间的比值(CPU总消耗时间=运行用户代码时间+垃圾收集时间),高吞吐量可以高效利用CPU时间),和ParNew的最大区别是GC自动调节策略
;,采用复制算法负责新生代的垃圾回收工作
,可以与 Serial Old , Parallel Old 垃圾回收器一起搭配工作。
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)
。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择
。- (可以使用 java -XX:+PrintCommandLineFlags -version 命令查看)JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能
- 虚拟机会根据系统的运行状态收集性能监控信息,动态设置这些参数,以提供最优停顿时间和最高的吞吐量
- Pararllel Scavenge是与 ParNew 类似,
都是用于年轻代回收的使用复制算法的并行的多线程收集器
,与 ParNew 不同的是,Parallel Scavenge 的目标是达到一个可控的吞吐量
。吞吐量=程序运行时间/(程序运行时间+GC时间)。如程序运行了99s,GC耗时1s,吞吐量=99/(99+1)=99%。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效的利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
- Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量:
- 控制最大垃圾收集(GC)停顿时间的-XX:MaxGCPauseMills参数。
- 直接设置吞吐量大小的-XX:GCTimeRatio参数
- Pararllel Scavenge是与 ParNew 类似,
- Serial:串行收集器。
老规矩,买一赠一
- 位于老年代中(相对来说年代久远一点的机器)
- CMS:是一种以
获得最短回收停顿时间【也可以说是非常符合在注重用户体验的应用上使用】为目标的收集器
,基于标记清除算法
,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。CMS可以说是一款具有"跨时代"意义的垃圾回收器,支持了和用户线程一起工作,做到了一起并发回收垃圾的"壮举"
。
- CMS(Concurrent Mark Sweep,并发标记清除) 收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿
- CMS是基于“标记-清除”算法实现的。
CMS 回收过程分为以下四步
:- 初始标记 (CMS initial mark):先 Stop The World,(
初始标记只是标记出来和 GC Roots 直接关联的对象,整个速度是非常快的,为了保证标记的准确,这部分会在 STW 的状态下运行
)主要是标记 GC Root 开始的下级(注:仅下一级)对象(标记一下GC Roots能直接关联到的对象
),但是跟 GC Root 直接关联的下级对象不会很多,因此这个过程其实很快 - 并发标记 (CMS concurrent mark):(
并发标记这个阶段会直接根据第一步关联的对象找到所有的引用关系
,同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。虽然耗时较长,但是不会有很大的影响。)根据上一步的结果,继续向下标识所有关联的对象,直到这条链上的最尽头。这个过程是多线程的,虽然耗时理论上会比较长,但是其它工作线程并不会阻塞,没有 Stop The World
。- 但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记(CMS remark):(重新标记是为了解决第二步并发标记所导致的标错情况,这里简单举个例子:并发标记时a没有被任何对象引用,此时垃圾回收器将该对象标位垃圾,在之后的标记过程中,a又被其他对象引用了,这时候如果不进行重新标记就会发生误清除。这部分内容也是在STW的情况下去标记的。)顾名思义,就是要再标记一次。为啥还要再标记一次?因为第 2 步并没有阻塞其它工作线程,其它线程在标识过程中,很有可能会产生新的垃圾。这个过程会Stop The World。还有一个原因就是为了修正并发标记期间因为用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
- 重新标记阶段就是为了
修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长
,远远比并发标记阶段时间短
- 重新标记阶段就是为了
- 并发清除(CMS concurrent sweep):清除阶段是清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段CMS收集器的内存回收过程也是可以与用户线程同时并发进行的。(这一步就是最后的清除阶段了,将之前真正确认为垃圾的对象回收,这部分会和用户线程一起并发执行。)
- 初始标记 (CMS initial mark):先 Stop The World,(
- CMS优点:
- 并发收集
- 低停顿
- CMS 的问题:
- 并发回收导致CPU资源紧张,影响用户线程的执行效率:在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四个时,CMS对用户程序的影响就可能变得很大(,
由于是和用户线程一起并发清理,那么势必会影响到用户线程的执行速度,并且这个影响随着核心线程数的递减而增加。所以 JVM 提供了一种 "增量式并发收集器"的 CMS 变种,主要是用来减少垃圾回收线程独占资源的时间,所以会感觉到回收时间变长,这样的话单位时间内处理垃圾的效率就会降低,也是一种缓和的方案
。) - .无法清理浮动垃圾:在CMS的并发标记和并发清理阶段,用户线程还在继续运行,就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留到下一次垃圾收集时再清理掉。这一部分垃圾称为“浮动垃圾”( CMS 真正清理垃圾是和用户线程一起进行的,在清理这部分垃圾的时候用户线程会产生新的垃圾,这部分垃圾就叫做浮动垃圾,并且只能等着下一次的垃圾回收再清除。)
- 并发失败(Concurrent Mode Failure):由于在垃圾回收阶段用户线程还在并发运行,那就还需要预留足够的内存空间提供给用户线程使用,因此CMS不能像其他回收器那样等到老年代几乎完全被填满了再进行回收,必须预留一部分空间供并发回收时的程序运行使用。默认情况下,当老年代使用了 92% 的空间后就会触发 CMS 垃圾回收,这个值可以通过 -XX**😗* CMSInitiatingOccupancyFraction 参数来设置。这里会有一个风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:Stop The World,临时启用 Serial Old 来重新进行老年代的垃圾回收,这样一来停顿时间就很长了
- 内存碎片问题:CMS是一款基于“标记-清除”算法实现的回收器,这意味着回收结束时会有内存碎片产生。内存碎片过多时,将会给大对象分配带来麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次 Full GC 的情况
- CMS 是使用了标记删除的算法去清理垃圾的,
而这种标记删除算法的缺点就是会产生碎片化,后续可能会导致大对象无法分配从而触发和 Serial Old 一起配合使用来处理碎片化的问题
,当然这也处于 STW的情况下,所以当 java 应用非常庞大时,如果采用了 CMS 垃圾回收器,产生了碎片化,那么在 STW 来处理碎片化的时间会非常之久。 - 为了解决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开关参数(默认开启),用于在 Full GC 时开启内存碎片的合并整理过程**,由于这个内存整理必须移动存活对象,是无法并发的,这样停顿时间就会变长。**还有另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数的作用是要求CMS在执行过若干次不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理(默认值为0,表示每次进入 Full GC 时都进行碎片整理)
- CMS 是使用了标记删除的算法去清理垃圾的,
- 并发回收导致CPU资源紧张,影响用户线程的执行效率:在并发阶段,它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低程序总吞吐量。CMS默认启动的回收线程数是:(CPU核数 + 3)/ 4,当CPU核数不足四个时,CMS对用户程序的影响就可能变得很大(,
- Serial Old:Serial收集器的老年代版本,
单线程收集器
,使用标记整理算法负责老年代的垃圾回收工作
,有可能还会配合 CMS 一起工作。
- 这个收集器的主要意义就是在于给Client模式下的虚拟机使用。
- 如果在Server模式下他还有两大用途:
- 在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
- Parallel Old:是Parallel Scavenge收集器的老年代版本,
使用多线程
,标记-整理算法负责新生代的垃圾回收工作
,可以与 Parallel Scavenge 垃圾回收器一起搭配工作。【在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器
】
- G1:标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收。不会产生空间碎片,可以精确地控制停顿;G1将整个堆分为大小相等的多个Region(区域),G1跟踪每个区域的垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收效率
- G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器【
G1主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
】。G1设计初衷就是替换 CMS,成为一种全功能收集器。G1 在JDK9 之后成为服务端模式下的默认垃圾回收器,取代了 Parallel Scavenge 加 Parallel Old 的默认组合,而 CMS 被声明为不推荐使用的垃圾回收器。G1从整体来看是基于 标记-整理 算法实现的回收器,但从局部(两个Region之间)上看又是基于 标记-复制 算法实现的 - G1(Garbage First):顾名思义,垃圾回收第一,官方对它的评价是在垃圾回收器技术上具有里程碑式的成果。
G1 回收的目标不再是整个新生代,不再是整个老年代,也不再是整个堆了。G1 可以面向堆内存的任何空间来进行回收,衡量的标准也不再是根据年代来区分,而是哪块空间的垃圾最多就回收哪块儿空间,这也符合 G1 垃圾回收器的名字,垃圾第一,这就是 G1 的 Mixed GC 模式
。当然虽然垃圾回收不根据年代来区分,但是 G1 还是根据年代来设计的,我们先来看下 G1 对于堆空间的划分:
- 但是这种方法也会有个问题,有可能垃圾回收的速度小于新对象分配的速度,这样会导致 “Full GC” 而产生长时间的 STW。
在 G1 的设计理念里,最小回收单元是 Region,每次回收的空间大小都是Region的N倍,那么G1是怎么选择要回收哪块儿区域的呢?G1 会跟踪各个 Region 区域内的垃圾价值,和回收空间大小回收时间有关,然后维护一个优先级列表,来收集那些价值最高的Reigon区域。
- 与其他GC收集器相比G1的特点:
- G1与CMS的区别:
或者说这是 G1 相对于 CMS 的另一个大优势
,降低停顿时间是 G1 和 CMS 共同的关注点
,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。- G1算法是可控STW的一 种算法,
GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数
。
- G1算法是可控STW的一 种算法,
- G1能充分利用多CPU、多核环境下的硬件优势。
- 分代收集:不需要其他收集器配合,
可以独立管理整个GC堆
,能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以及获取更好的收集效果。 - 空间整合:G1从整体看是基于标记整理算法实现的收集器,从局部(两个Region之间)看是基于复制算法实现的。
G1运作期间不会产生内存空间碎片
,收集后可以提供规整的可用内存。- G1将这个Java堆分为多个大小相同的独立区域(Region),虽然还保留有新生代和老年代的概念,但是新生代和老年代已经不再是物理隔离得了,他们都是一部分Region(不需要是连续的)的集合
- G1将这个Java堆分为多个大小相同的独立区域(Region),虽然还保留有新生代和老年代的概念,但是新生代和老年代已经不再是物理隔离得了,他们都是一部分Region(不需要是连续的)的集合
- 可预测的停顿:
能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒
。【可以精确的控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。】- G1收集器之所以能够建立可预测的停顿时间模型是因为G1可以
有计划的避免在整个Java堆中进行全区域的垃圾收集
。
- G1收集器之所以能够建立可预测的停顿时间模型是因为G1可以
- G1与CMS的区别:
- G1 回收器的运作过程大致可分为四个步骤:
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)
。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)- 初始标记(会STW):
仅仅只是标记一下 GC Roots 能直接关联到的对象
,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿- 标记出来 GC Roots 能直接关联到的对象
修改 TAMS 的值
以便于并发回收时新对象分配- 是在 Minor GC 时期(STW)完成的
- 并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象
- 根据刚刚关联的对像扫描整个对象引用图,和用户线程并发执行
- 记录 SATB(原始快照) 在并发时有引用的值
- 最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。(修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录)
- 处于 STW,处理第二步遗留下来的少量 SATB(原始快照) 记录
- 清理阶段(筛选回收)(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的
- 维护之前提到的优先级列表
- 根据优先级列表,用户设置的最大暂停时间来回收 Region
- 将需要回收的 Region 内存活的对象复制到不需要回收的 Region区域内,然后回收需要回收的 Region
- 这部分是处于 STW 下执行,并且是多线程的
老规矩,买一赠一
打手:新生代里面的大块头和老年代里面的大块头是按设备新旧来划分的嘛,我咋感觉不是呢,而且还有一个居中的G1呢
保洁组长:肯定不是啦,我就是为了让你好记,到时候活没干好分不清是谁的锅。
哦对了,说起这个G1,算是我们对里比较优秀的一个了,他的工作过程也比较独特,就在图里面写着呢,你可以瞅瞅。
- 初始标记(会STW):
- G1(Garbage First)回收器采用面向局部收集的设计思路和基于Region的内存布局形式,是一款主要面向服务端应用的垃圾回收器【
- CMS:是一种以
- JVM中一次完整的GC:
- 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ),
新生代默认占总空间的 1/3,老年代默认占 2/3
。 新生代由 3 个分区组成:Eden、To Survivor、From Survivor
,它们的默认占比是 8:1:1。- 新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收
- 老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法
老规矩,赠品增增增不停:
打手:行啦行啦,贴这么多图,这大块头懂得差不多了。那你们这具体是咋打扫呢,别空有一身漂亮的皮囊。
保洁组长:好好好,还猴急的不行。
保洁组长:我们这根据你这边给的钱的多少分为轻轻描边扫和重重锤地霹雳胖浪扫
打手,啥…还分轻重,你这也没说呀,你这不坑人吗,
保洁组长:先别急嘛,我们这有新手福利,你第一次不会受损的…
打手:切,信nen个鬼哟
保洁队长:先别急,让我装完再说。这是我们的打扫计划
- 针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
- Partial GC:
并不收集
整个GC堆的模式- Young GC:也叫Minor GC。只收集young gen的GC
- young GC触发条件:当
young gen中的eden区分配满
的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。
- young GC触发条件:当
- Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
- 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
- Mixed GC:收集整个young gen以及部分old gen的GC。
只有G1有这个模式
- Young GC:也叫Minor GC。只收集young gen的GC
- Full GC:又叫full GC。
收集整个堆
,包括young gen、old gen、perm gen (如果存在的话)等所有部分的模式。
- Partial GC:
- Minor GC 和 Full GC 有什么不同呢?
- Minor GC:只收集新生代的GC。 Full GC: 收集整个堆,包括 新生代,老年代,永久代(在 JDK 1.8及以后,永久代被移除,换为metaspace 元空间)等所有部分的模式。
- Minor GC触发条件:当Eden区满时,触发Minor GC。
- Full GC触发条件:
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。
- 因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC) ;
- 老年代空间不够分配新的内存(或永久代空间不足,但只是JDK1.7有的,这也是用元空间来取代永久代的原因,可以减少Full GC的频率,减少GC负担,提升其效率)。
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
- 调用System.gc、heap dump带GC时,系统建议执行Full GC,但是不必然执行
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。
- 在 Java 中,堆被划分成两个不同的区域:
- 新生代 ( Young ):新生代默认占总空间的 1/3。新生代有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
- 新生代的垃圾回收**(又称Minor GC)后只有少量对象存活,所以选用复制算法**,只需要少量的复制成本就可以完成回收
- 老年代 ( Old ):老年代默认占 2/3。
- 老年代的垃圾回收**(又称Major GC)通常使用“标记-清理”或“标记-整理”**算法
- 新生代 ( Young ):新生代默认占总空间的 1/3。新生代有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1
一次完整的垃圾回收的具体的流程
如下:【其中也包含了几条内存回收和分配原则
】对象优先在Eden分配
。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC- 接下来的玩法:
- 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区;
- Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区;
- 移动一次,对象年龄加 1【
对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁
】,对象年龄大于一定阀值会直接移动到老年代
。GC年龄的阀值可以通过参数 -XX:MaxTenuringThreshold 设置,默认为 15;- 既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,
虚拟机给每个对象一个对象年龄(Age)计数器
。 - 关于默认的晋升年龄是 15,这个说法的来源大部分都是《深入理解 Java 虚拟机》这本书。 如果你去 Oracle 的官网阅读相关的虚拟机参数,你会发现-XX:MaxTenuringThreshold=threshold这里有个说明:Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.
默认晋升年龄并不都是 15,这个是要区分垃圾收集器的,CMS 就是 6
- 既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,
- 动态对象年龄判定:Survivor 区相同年龄所有对象大小的总和 > (Survivor 区内存大小 * 这个目标使用率)时,大于或等于该年龄的对象直接进入老年代。其中这个使用率通过 -XX:TargetSurvivorRatio 指定,默认为 50%;
- “Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时(默认值是 50%,可以通过 -XX:TargetSurvivorRatio=percent 来设置,参见 issue1199 ),取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
Survivor 区内存不足会发生担保分配,超过指定大小的对象可以直接进入老年代
- 咱们可以写例子程序然后看看分区情况,随便在main函数中定义一个数组啥的,然后
添加参数:-XX:+PrintGCDetails
,然后控制台就有东西打印出来了,细细品吧
- 接下来的玩法:
大对象直接进入老年代
,大对象就是需要大量连续内存空间的对象(比如:字符串、数组),为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率- 长期存活的对象将进入老年代。
- 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和老年代
- 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ),
打手,快继续,你这很明显没说完嘛
打手:虽然挺细的,但是我还有不知道,你们这怎样规划我们这里不同类的垃圾呢,分类好再进行回收。
保洁组长:你还真是要把我榨干呀。行行行,给你再补充一点。
- STW:Stop-The-World机制简称 STW。是在执行垃圾收集算法时,
Java 应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)
。Java 中一种全局暂停现象,全局停顿,所有 Java 代码停止,native 代码可以执行,但不能与 JVM 交互
。- 为什么需要 STW:
- 在 java 应用程序中引用关系是不断发生变化的,
那么就会有会有很多种情况来导致垃圾标识出错
。想想一下如果 Object a 目前是个垃圾,GC 把它标记为垃圾,但是在清除前又有其他对象指向了 Object a,那么此刻 Object a 又不是垃圾了,那么如果没有 STW 就要去无限维护这种关系来去采集正确的信息
。再举个例子,到了秋天,道路上洒满了金色的落叶,环卫工人在打扫街道,却永远也无法打扫干净,因为总会有不断的落叶
- 在 java 应用程序中引用关系是不断发生变化的,
- 为什么需要 STW:
- 对象的内存分配与回收策略:
- 内存分配:
- 回收策略:
补充图:- 3.长期存活的对象进入老年代:结合图看。对象在Survivor区中每熬过一次Minor GC年龄就增加一岁,当对象的的年龄增加到一定程度(默认为15随)就会被晋升到老年代。定义年龄的阈值其实也就是定义对象晋升老年代的年龄阈值
- 5.空间分配担保原则:类似于贷款,收不回来帐时就从你的担保人卡里扣【
空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间
。】
- 如果YougGC时新生代有大量对象存活下来,而 survivor 区放不下了,这时必须转移到老年代中,但这时发现老年代也放不下这些对象了,那怎么处理呢?其实JVM有一个老年代空间分配担保机制来保证对象能够进入老年代。在执行每次 YoungGC 之前,JVM会先检查老年代最大可用连续空间是否大于新生代所有对象的总大小。因为在极端情况下,可能新生代 YoungGC 后,所有对象都存活下来了,而 survivor 区又放不下,那可能所有对象都要进入老年代了。这个时候如果老年代的可用连续空间是大于新生代所有对象的总大小的,那就可以放心进行 YoungGC。但如果老年代的内存大小是小于新生代对象总大小的,那就有可能老年代空间不够放入新生代所有存活对象,这个时候JVM就会先检查 -XX:HandlePromotionFailure 参数是否允许担保失败,如果允许,就会判断老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次YoungGC,尽快这次YoungGC是有风险的。如果小于,或者 -XX:HandlePromotionFailure 参数不允许担保失败,这时就会进行一次 Full GC
- 在允许担保失败并尝试进行YoungGC后,可能会出现三种情况
- YoungGC后,存活对象小于survivor大小,此时存活对象进入survivor区中
- YoungGC后,存活对象大于survivor大小,但是小于老年大可用空间大小,此时直接进入老年代
- YoungGC后,存活对象大于survivor大小,也大于老年大可用空间大小,老年代也放不下这些对象了,此时就会发生“Handle Promotion Failure”,就触发了 Full GC。如果 Full GC后,老年代还是没有足够的空间,此时就会发生OOM内存溢出了
- 内存分配:
- jdk1.8默认的垃圾回收器是什么:
- jdk1.7默认垃圾收集器Parallel Scavenge (新生代) +Parallel Old (老年代)
- jdk1.8 默认垃圾收集器Parallel Scavenge ( 新生代) +Parallel Old (老年代)
- jdk1.9默认垃圾收集器G1
- jdk10默认垃圾收集器G1
- jdk11默认垃圾收集器G1
保洁:给你展示的还行吧
打手:还可以,一般般吧,快扫吧…(辅助着学还行吧,深度还是不足哟)
10分钟后…
保洁队长:好了,你看看。
打手:还凑合吧,可以了,要不一块去吃过饭,庆一下功。
保洁员:你…掏钱?
打手:嗯…哎。老胡,带上你家敏敏,一块吃个饭呗
敏小言和小胡夫妇:中中中,go
打手:我给你们说…我听说还有个啥
- Springboot
- 和多线程和高并发
的故事呢?讲吗?
我:讲讲讲,继续有图有真相,谝起来,改天再见喽
巨人的肩膀:
深入理解Java虚拟机