【JVM】GC模型(知识梳理)

垃圾判断算法

引用计数算法

给对象添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。
引用计数算法无法解决对象循环引用的问题。
对象循环引用就是:堆里的两个对象相互引用,当外部已经没有任何来自栈的引用时由于两者相互引用导致本应该被标记为垃圾的对象仍然存活下去

根搜索算法

在实际的生产语言中(Java、 C#等)都是使用根搜索算法判定对象是否存活
算法基本思路就是通过一系列的称为“GCRoots"的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链( Reference Chain)相连,则证明此对象是不可用的。
 做为GC Roots根节点的对象主要是在全局性的引用(如常量、类静态属性)和执行上下文中(如栈帧中的本地变量表),现在的很多应用仅方法区就有数百兆,逐个检查里边的引用显然很耗费时间。

枚举GC Roots根节点:
  在HotSpot实现中,利用了空间换取时间,是使用一组OopMap的数据结构来完成的。类加载完成后,会把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,在特定的位置(即安全点)使用OopMap记录下栈和寄存器哪些位置是引用,这样GC发生的时候就不用全部扫描了。

根搜索算法( GC RootsTracing ):
在Java语言中,GC Roots包括
1.在VM栈(帧中的本地变量)中的引用
2.方法区中的静态引用
3.JNI (即一般说的Native方法)中的引用

也就是说不管引用链子多复杂,即使存在循环引用,只要最终入口来自栈或元空间等非堆区域,那么他就一定会被搜索到!

Java虚拟机规范表示可以不要求虚拟机在这区实现GC,这区GC的“性价比”一般比较低.
在堆中,尤其是在新生代,常规应用进行一次GC-般可以回收70%~95% 的空间,而方法区的GC效率远小于此。
当前的商业JVM都有实现方法区的GC,主要回收两部分内容:废弃常量与无用类
类回收需要满足如下3个条件
1.该类所有的实例都已经被GC, 也就是JVM中不存在该Class的任何实例
2.加载该类的ClassL oader已经被GC
3.该类对应的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

GC算法

标记——清除

算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,然后回
收所有需要回收的对象。
缺点:
效率问题,标记和清理两个过程效率都不高,需要扫描所有对象
空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作

标记——整理

标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象端移动,然后直接清理掉这端边界以外的内存。

复制算法

将可用内存划分为两块,每次只使用其中的一块,当半区内存用完 了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉
这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。
只是这种算法的代价是将内存缩小为原来的一半,代价高昂

现在的商业虛拟机中都是用了这一种收集
算法来回收新生代将内存分为块较 大的eden空间和2块较少
的survivor空间, 每次使用eden和其中一块survivor, 当回收时将eden和survivor还存活的对象一次性拷贝到另外一块survivor空
间上,然后清理掉eden和用过的survivor。
Oracle Hotspot虛拟机默认eden和survivor的大小比例是8:1,也就是每次只有10%的内存是“浪费”的!

复制收集算法在对象存活率高的时候,效率有所下降
如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

分代算法

当前商业虚拟机的垃圾收集都是采用“分代收集”(Generational Collecting) 算法,根据对象不同的存活周期将内存划分为几块。
一般是把Java堆分作新生代和老年代, 这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。

也就是说,标记清除需要变量全部堆中对象,虽然每次标记很快,但是总工作量大
复制算法,只需将存活的对象复制到另一个区域即可,和标记清除比起来,复制算法每个对象复制的操作比标记多但是不会产生碎片化空间。
所以当总工作量较小时用复制算法性价比最高!一旦工作量大了,那复制的操作会使得效率更低!

三个代:

●年轻代(Young Generation )
新生成的对象都放在新生代。年轻代用复制算法进行GC (理论上,年轻代对象的生命周期非常短,所以适合复制算法)
年轻代分三个区。一个Eden区,两个Survivor区(可以通过参数设置Survivor个数)。对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到一个Survivor区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当第二个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。2个Survivor是 完全对称,轮流替换。
Eden和2个Survivor的缺省比例是8:1:1,也就是10%的空间会被
浪费。可以根据GClog的信息调整大小的比例

●老年代(Old Generation)
存放了经过次或多次GC还存活的对象
一般采用Mark- Sweep或者Mark-Compact算法进行GC
有多种垃圾收集器可以选择。每种垃圾收集器可以看作一个GC算法的具体实现。可以根据具体应用的需求选用合适的垃圾收集器

永久代(元空间)
并不属于堆(Heap).但是GC也会涉及到这个区域存放了每个Class的结构信息,包括常量池、字段描述、方法描述。与垃圾收集要收集的Java
对象关系不大。

内存回收

GC要做的是将那些dead的对象所占用的内
存回收掉
●Hotspot认为没有引用的对象是dead的
●Hotspot将引用分为四种: Strong、 Soft、Weak、 Phantom
Strong即默认通过Object o=new Object()这种方式赋值的引用
Soft、Weak、 Phantom这 三种则都是继承Reference
在Full GC时会对Reference类型的引用进行特殊处理
●Soft:内存不够时一定会被GC、长期不用也会被GC
●Weak:一定会被GC,当被mark 为dead,会在ReferenceQueue中通知
Phantom:本来就没引用,当从jvm heap中释放时会通知

内存分配:
1、堆上分配
大多数情况在eden,上分配,偶尔会直接在old,上分配细节取决于GC的实现
2、栈上分配
原子类型的局部变量

GC的时机

在分代模型的基础上,GC从时机上分为两种: Scavenge GC和Full GC
Scavenge GC (Minor GC)
触发时机:新对象生成时,Eden空间满了理论上Eden区大多数对象会在ScavengeGC回收,复制算法的执行效率会很高,ScavengeGC时间比较短。
Full GC
对整个JVM进行整理,包括Young、Old 和Perm
主要的触发时机: 1) Old满了2) Perm满了3) system.gc()
效率很低,尽量减少Full GC。

垃圾收集器详述

垃圾回收器(Garbage Collector):
分代模型: GC的宏观愿景;
垃圾回收器:GC的具体实现
Hotspot JVM提供多种垃圾回收器,我们需要根据具体应用的需要采用不同的回收器。
没有万能的垃圾回收器,每种垃圾回收器
都有自 己的适用场景。

垃圾收集器的“并行”和“并发:

并行( Parallel) :指多个收集器的线程同时工作,但是用户线程处于等待状态
并发( Concurrent):指收集器在工作的同时,可以允许用户线程工作。
并发不代表解决了GC停顿的问题,在关键的步骤还是要停顿。比如在收集器标记垃圾的时候。但在清除垃圾的时候,用户线程可以和GC线程并发执行。

Serial收集器

●单线程收集器,收集时会暂停所有工作线程(Stop The World,简称STW),使用复制收集算法,虚拟机运行在Client模式时
的默认新生代收集器。

最早的收集器,单线程进行GC
New和Old Generation都可以使用
在新生代,采用复制算法:在老生代,采用Mark- Compact算法
因为是单线程GC,没有多线程切换的额外开销,简单实用
Hotspot Client模式缺省的收集器

ParNew收集器

ParNew收集器就是Serial的多线程版本,除了使用多个收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器模一 样。
对应的这种收集器是虛拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果

使用复制算法(因为针对新生代)
只有在多CPU的环境下,效率才会比Serial
收集器高

可以通过-XX:ParallelGCThreads来控制GC线程数的多少。 需要结合具体CPU的个数
Server模式下新生代的缺省收集器

Parallel Scavenge收集器

Parallel Scavenge收集器也是一个多线程收集器,也是使用复制算法,但它的对象,分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。

Parallel Old收集器

老年代版本吞吐量优先收集器,使用多线程和标记一整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合,工作。

1.Parallel Scavenge在老年代的实现
2.在JVM 1.6才出现Parallel Old
3.采用多线程,Mark-Compact算法
4.更注重吞吐量
5.Parallel Scavenge + Parallel Old =高吞吐量,但GC停顿可能不理想

CMS ( Concurrent Mark Sweep ) 收集器

CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,CMS收集器使用的是标记一清除算法

CMS收集器(Concurrent Mark Swap)
追求最短停顿时间,非常适合Web应用只针对老年区,一般结合ParNew使用
Concurrent, GC线程和用户线程并发工作(尽量并发)
Mark- Sweep
只有在多CPU环境下才有意义
使用-XX: +UseConcMarkSweepGC打开

CMS收集器的缺点
●CMS以牺牲CPU资源的代价来减少用户线程的停顿。当CPU个数少于4的时候,有可能对吞吐量影响非常大
●CMS在并发清理的过程中,用户线程还在跑。这时候需要预留一部分空间给用户线程
●CMS用Mark: Sweep,会带来碎片问题。碎片过多的时候会容易频繁触发FullGC

CMS收集器对CPU资源非常敏感。
CMS收集器无法处理浮动垃圾( Floating Garbage),可能出现“ Concurrnet
Mode Failure"失败而导致另一次Full GC的产生。如果在应用中老年代增长不是太快,可以适当调高参数-XX: CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次數从而获取更好的性能。要是CMS运行期间预留的内存无法满足程序需要时,虚拟机将启动后备预案:临时启用SerialOld收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数.XX: CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure"失败,性能反而降低。收集结束时会有大量空间碎片产生,空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前进行一次Full GC。
也就是说,参数设置过高导致CMs触发次数降低,浮动垃圾就多,CMS太多触发又会导致停顿时间和大量碎片空间的问题。所以采用预备方案SerialOld收集器来清理

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

G1收集器:

G1 Garbage Collector
g1收集器是一个面向服务端的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。
它满足短时间gc停顿的同时达到一个较高的吞吐量。
JDK7以上版本适用。

G1收集器的设计目标
1.与应用线程同时工作,几乎不需要stop theworld(与CMS类似);
2.整理剩余空间,不产生内存碎片( CMS只能在FullGC时,用stop the world整理内存碎片)
3.GC停顿更加可控;不牺牲系统的吞吐量;
4.gc不要求额外的内存空间(CMS需要预留空间存储浮动垃圾);

G1收集器堆结构
G1使用了gc停顿可预测的模型,来满足用户设定的gc停顿时间,根据用户设定的目标时间,G1会自动地选择哪些region要清除,一次清除多少个region
G1从多个region中复制存活的对象,然后集中放入一个region中,同时整理、清除内存(copying收集算法)

G1 VS CMS
1.对比使用mark-sweep的CMS,G1使用的copying算法不会造成内存碎片;
2.对比Parallel Scavenge(基于copying )、Parallel Old收集器(基于mark compact-sweep),Parallel会对整个区域做整理导致gc停顿会比较长,而G1只是特定地整理几个region。
3.G1并非一个实时的收集器,与parallelScavenge样, 对gc停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的gc停顿时间。与之前gc收集器对比,G1会根据用户设定的gc停顿时间,智能评估哪几个region需要被回收可以满足用户的设定

G1重要概念
分区(Region):G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题G1将整个堆分成相同大小的分区(Region )

官方文档

每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。年轻代、 幸存区、老年代这些概念还存在,成为逻辑,上的概念,这
样方便复用之前分代框架的逻辑。
在物理上不需要连续,则带来了额外的好处,有的分区内垃圾对象特别多,有的分区内垃圾对象很少**,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来**,即首先收集垃圾最多的分区。

对这一点详细说明:
g1优先回收存活对象少的空间是因为,g1的分区导致每块区大小相同,所以同种类的区域存活对象越少,证明垃圾越多或者空闲空间越多,空闲空间多的情况会在g1并发清理时取消标记把他们加入freelist里供接下来分配使用,而存活对象少的区域就剩了垃圾多的这一种情况,那么g1会优先收集他们,g1清除了这些垃圾多的区域后会合并这些存活的对象,由于采用复制算法,复制存活对象越少消耗时间越低,就可以在规定时间内完成更多空间回收;

假设同种类区域A区和B区,A去30%是存活对象其余为垃圾,B区5%是存活对象其余是垃圾,由于这俩区域总大小相同,在回收时采用复制算法,清理这俩区域得到的最终结果是大小相同的俩区域,那肯定是B区工作量小,B区域仅仅只用复制很少的对象就能得到这块完整的区域,消耗的时间就少,在g1这种回收时STW时间可预期控制的收集器中,肯定是先收集存活对象少的区域带来更高的性价比!

也正是这种区域的划分成一块块的,这样在老年代采用复制算法得到可能,若是按照之前的区域划分,老年代采用复制算法会消耗大量时间,因为存活对象太多,而G1的区域都是一块一块的,种类不同,所以当某一块种类为老年代的区域因为存活对象少的而被回收时,仅仅只用复制这一点对象给其他区即可,工作量并不大!

正因为这种分区模式,使得g1可以根据场景动态的调整eden sur old区域大小,在某一时间可能一块区域属于0ld区域,但是该区域如果经历过回收后,下一时间段他就可以用于其他种类(sur eden)

G1 GC模式
G1提供了两种GC模式,Young GC和Mixed GC,
两种都是完全Stop The World的
Young GC:选定所有年轻代里的Region。通过控制年轻代的Region个数,即年轻代内存大小,来控制Young GC的时间开销。
Mixed GC:选定所有年轻代里的Region,外加根据global
concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region
Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行MixedGC,
就会使用serialold GC (Full GC)来收集整个GC heap。所以本质上,G1是不提供Full GC的

global concurrent marking
初始标记(initial mark, STW) :它标记了从GC Root开始,直接可达的对象。
并发标记( Concurrent Marking) :这个阶段从GC Root开始对heap中的对象进行标记,标记线程与应用程序线程并发执行,并且收集各个Region的存活对象信息。
重新标记(Remark, STW) :标记那些在并发标记阶段发生变化的对象,将被回收。
清理(Cleanup):清除空Region(没有存活对象的),加入到free list.

G1在运行过程中的主要模式
YGC(不同于CMS)
并发阶段
混合模式
Full GC
(一般是G1出现问题时发生)

G1 YGC在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空白,即不属于任何一个分区( Eden、Survivor、 Old )

Rest:每个区域都有一个Rest集合用于表示谁指向我,键为其他区域地址,值为该区域中的哪些元素指向我区域的某一元素,这整个字段下标就是我区域中某个元素下标,集合该区域不管是老年代还是青年代,在可达性分析时如何判断他是不是根对象?g1提供了rest在回收一个Region的时候不需要执行全堆扫描,只需要检查它的RS就可以找到外部引用,而这些引用就是initial mark的根之一。
而且Rset中记录的数据越少说明存活对象越少,被选中优先清理的可能性就更大!假如没有外部的指针指向这个区域,就可以直接回收整块区域而不用进行内存Copy

G1的适合场景
服务端多核CPU、JVM内存占用较大的应用
应用在运行过程中会产生大量内存碎片、需要经常压缩空间
想要更可控、可预期的GC停顿周期:防止高并发下应用的雪崩现象

吞吐量

吞吐量关注的是,在一个指定的时间内,最大化一个应用的工作量。如下方式来衡量一个系统吞吐量的好坏:在一小时内同一个事务(或者任务、请求)完成的次数(tps)数据库一小时可以完成多少次查询对于关注吞吐量的系统,卡顿是 可以接受的,因为这个系统关注长时间的大量任务的执行能力,单次快速的响应并不值得考虑。

响应能力

响应能力指一个程序或者系统对请求是否能够及
时响应,比如:一个 桌面UI能多快地响应一 个事件个网站能够多快返回一个页面请求数据库能够多快返回查询的数据对于这类对响应能力敏感的场景,长时间的停顿是无法接受的。

空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。当大量对象在MinorGC后仍然存活,就需要老年代进行空间分配担保,把Survivor无法容纳的对象直接进入老年代。如果老年代判断到剩余空间不足( 根据以往每次回收晋升到老 年代对象容量的平均值作为经验值),则进行一次FullGC。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值