JVM常见垃圾回收器及算法

1.什么是垃圾?

没有任何引用指向的一个对象或者多个对象(循环引用)

2.如何定位垃圾?

	1.引用计数(ReferenceCount)
		对象上存放引用它的引用数量,每当有地方引用它时,计数+1,去掉引用时,计数-1,当计数为0时,则认为是垃圾。但这种方案找不到循环引用的垃圾
	2.根可达算法(RootSearching)
			以静态变量、线程栈中变量、常量池(指向Class对象)、JNI指针(指向native方法用到的类或对象),作为根引用,顺着根引用能找到的对象都不是垃圾,其他的都是垃圾,Hotspot就是使用的这种方式定位垃圾

3.常见的垃圾回收算法

	1. 标记清除(mark sweep)
	含义:
		1. 两边扫描:
			1. mark:将跟对象可以访问到的对象都打上一个标识,表示可达。
			2. sweep:遍历堆内存,将不可达的对象清理
		 2. 产生内存碎片
		 3. 存活对象多时,效率高,因为需要清理的少	
	优缺点:
		位置不连续,产生碎片,效率偏低(两遍扫描)
		
	2. 拷贝算法(copying)
	含义:
		1. 将堆内存对半分成两个半区,只用其中一个半区进行对象分配,如果这个半区不够新对象分配了,就开始进行垃圾收集,将这个半区中所以可达的对象复制到另一个半区,然后回收这个半区。
		2. 需要移动和复制对象,因此对同一个对象需要修改引用
		3. 存活对象少,效率高,因为需要复制的对象少
	优缺点:
		没有碎片,浪费空间
	
	3. 标记压缩(mark compact)
	含义:
		1.两边扫描
			1.mark:将根对象可以访问的都打上标识,标识可达
			2.compact:移动所有可达对象到堆内存的同一个区域,使它们紧凑的排列再一起,从而将所有不可达对象释放出来的空间集中在一起。
		2. 没有碎片,也不会产生内存减半
		3. 效率低
			1. 算法复杂
			2. 多线程移动还需要时间进行同步
		
	优缺点:
		没有碎片,效率偏低(两边扫描,指针需要调整)

4.堆内存逻辑分区()

在这里插入图片描述

1. 从分代GC的角度看,堆分为新生代与老年代
2. 有些垃圾回收器逻辑上并不是如此划分的
	1. 除Epsilon、ZGC、Shenandoah之外的垃圾回收器 都是使用的逻辑粉黛模型
	2. G1 是逻辑分代,无聊不分代
	3. 除此之外,不仅是逻辑分代,而且物理分代
3. 新生代 = eden + 2个survivor区
	1.YGC(Minor GC): 无法在新生代为对象分配空间时产生,对新生代的内容进行回收,由于新生代内容通常大部分都能被回收掉,因此YGC采用拷贝算法进行回收,而拷贝算法需要额外空间,因此产生了survivor区
	2. 第1次YGC:大多数的对象都会被回收,eden -> s0
	3. 第2次YGC: eden + s0 -> s1
	4. 第3次YGC: eden + s1 -> s0
	5. 年龄足够:老年代
		1. 年龄指对象复制的次数
		2. -XX:MaxTenuringThreshold:设置年龄阈值
		3. PS默认15,因为gc的age是4位,最大是15,CMS是6,G1是15
4. 老年代
		1. FGC(Major GC、Full GC):老年代满了会产生FGC , 整个内存都会回收,效率低,会暂停所有当前线程,产生STW(stop-the-world)停顿,进行垃圾清理
		2. FGC本身采用标记压缩算法
5. GC Tuning(Generation): 尽量减少FGC
6. -Xmn : 指定新生代内存大小,-Xms:指定堆内存最小值,-Xmx:指定堆内存最大值

对象分配过程

在这里插入图片描述

7. 栈上分配。TLAB(thread Local Allocation Buffer) 线程本地分配
		1. 一些小的、无逃逸的、线程私有的对象,会使用标量替换,以标量的形式将该对象存放于栈内存中,而不是在堆内存中
				1. 无逃逸:某个对象的引用不会传递给其他线程或方法拿到
				2. 线程私有:不是线程私有,就意味着改对象一定会被其他线程访问到,也就一定是逃逸对象了。
				3. 标量替换:例如User会被替换为一个int和一个String
		2. 栈上分配比堆上分配更快,栈中的内存不需要垃圾回收,因为变量出栈,就没了。
		3. TLAB(Thread Local Allocation Buffer) 线程本地分配
			1. 为了防止各线程堆堆内存的征用导致效率降低
			2. 提前为每个线程分配独有的一块空间,eden区的1% ,线程分配空间时,先往这块空间里分配

常见的垃圾回收器
在这里插入图片描述

	8.常见垃圾回收器
	
		并发和并行的概念:
		并行:指多个垃圾回收线程同时工作,但是此时用户线程仍处于等待状态。
		并发:指多个垃圾手机现场和用户线程同时执行
		
		STW(Stop The World)的概念:
		JVM在后台自动发起和自动完成的,在用户不可见的情况下,当进行垃圾回收时,把用户正常的工作线程全部停掉,即GC停顿;
		
		吞吐量(Throughput)是垃圾收集时用户线程的停顿时间。
		吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)
		
		1. Serial: 串行回收,YGC时,所有线程停止工作STW,启动一个单线程的垃圾回收器开始执行,回收完垃圾后,其他线程重新执行
				1. safe point : 线程不是马上就能停止,会到一个安全点,才能停
				2. 早期内存小的时候(几十M至一两百M),一个线程做清理能很快清理完 ,对现在大内存的系统效率太低了,因此用的极少。
				3. 特点:复制算法、单线程、新生代
		2. Serial Old
				Serial 收集器的老年代版本
				1. 特点 :标记-整理算法、单线程、老年代
				2. 优点:效率高
				3. 缺点:会Stop The World

Serial/Serial Old收集器运行示意图如下:
在这里插入图片描述

		3. Parallel Scavenge(清理) :简称PS,并行回收,启动多个垃圾回收线程同时进行垃圾回收
				1. 特点: 复制算法、多线程收集(并行),新生代
				2. 优点:可以控制吞吐量
				3. 缺点:会Stop The World
			4. Parallel Old
					Parallel Scaveneg 收集器的老年代版本
					特点:标记-整理、多线程收集(并行)、老年代

Parallel Scavenge/Parallel Old收集器运行示意图如下:
在这里插入图片描述

			5. ParNew
				 是Serial 收集器的多线程版本,与 PS 功能相同,但可以与CMS配合,而PS不行。因为Parallel Scavenge(以及G1) 都没有使用传统的GC 收集器代码框架,而另外单独实现,而其余的几种收集器 则共用了部分的框架代码;

ParNew 收集器/ Serial Old 收集器运行示意图
在这里插入图片描述

			三色标记算法可能会出现漏标
			漏标的两个充要条件:
				1.		至少一个黑色对象在自己被标记之后指向了这个白色对象
				2.	有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用
			6. CMS(Concurent Mark Sweep)
				并发回收 : 所谓并发是指垃圾回收线程可与工作线程同时进行,这些线程可能处于同一cpu,所以是并发的,降低STW的时间至200ms 以内,并发是因为无法人数STW ,10G内存,ps + po , 一次STW 需要11s 。使用cms , 之后的FGC,用了几天或者几个小时时间。
					1. 诞生了一个里程碑一样的东西,毛病大,没有一个版本的jdk 默认使用CMS 
					2. 算法 : 三色标记 + Increment Update(增量更新)
							 增量更新破坏的是漏标的第一个条件,我们在这个黑色对象增加了对白色对象的引用之后,将它的这个引用,记录下来,在最后标记的时候,再以这个黑色对象为根,对它的引用进行重新扫描
					3. CMS几个阶段 :
							1. 初始标记 :STW , 单线程,只找根引用对象 并标记,所以很快
							2.  并发标记 :一边标记可达对象,一边产生垃圾,80% GC时间,都浪费在这里,这块和用户线程同时运行。
							3. 重新标记:STW , 多线程,并发标记同时产生新的可达对象,这些新对象需要被重新标记,时间也不长
							4. 并发清除:不需要移动存活对象,因此可以与工作线程同时进行,清理过程中产生的垃圾,叫浮动,浮动垃圾等下一个周期进行清理。
						4.CMS 缺点:
								1. 内存的碎片化:由于采用标记清理算法,因此CMS 在只当参数次(默认每次)FGC后,下一次FGC前,会启动单线程Serial Old对内存精细合并整理,浪费时间
								2. 浮动垃圾:
									1. 由于CMS 与工作线程同时执行,因此不能等内存满了才进行CMS,老年代使用内存占老年代总嗯IC的一定百分比就开始CMS, jdk5 默认 68%,jdk6默认92%
									2. 如果预留的着8%无法满足工作线程分配内存的需要,会引起并发失败(垃圾回收与工作线程同时处理失败Concurrent Mode Failure)
									3. 此时系统不得不启动后备方案,STW,然后启用Serial Old 进行清理
									4. 可以降低触发CMS 的阈值,-XX:CMSInitiatingOccupanycyFraction
							
							7. G1 
								 STW 10ms,
								 算法:三色标记+ SATB(原始快照)
								 		原始快照破坏的是漏标的第二个条件,我们对这个合适对象取消对白色对象的引用之前,将这个引用记录下来,再最后标记的时候,再以这个引用指向的白色对象为根,对他的引用进行扫描
							8. ZGC 
								STW 1ms,算法:coloredpointer(颜色指针)+写屏障
							9. Shenandoah
								算法 : Colored Pointers + 读屏障 
								
			总结:垃圾收集器与内存大小的关系
					1. Serial : 几十M
					2.  PS : 上百M - 几个G
					3. CMS : 20G
					4. G1 :上百G
					5. ZGC: 最高4T

G1详解

		1. G1将内存划分为多个(默认2000多个)大小相同的内存块称为Region,每个Region是逻辑连续的一段内存,在被使用时都充当一种角色

在这里插入图片描述

2. Region可充当old、survivor、eden、humongos,当新建对象大小超过Region的50%,直接在新的一个或多个连续Region中分配,并标记为humongos区,某Region的角色不是固定的edian或old,随时会变化
3. 当垃圾回收时,优先收集存活对象最少的,也就是垃圾最多的Region,所以叫garbage first
4. 特点
	1. 并发收集,与CMS差不多,采用三色标记算法
	2. 压缩空闲空间,不会延长GC的暂停时间
	3. 每次收集指定的几个Region,因此更容易预测GC暂停时间
	4. 适用于不需要实现很高吞吐量的场景,使用硬件弥补吞吐量的问题
5. 当YGC时,正常来讲,只需要先找到Young区中的所有root对象,再顺着这些root对象继续找,找到的所有对象就是可达对象,剩余对象就全可以清理,但有一种情况,如果老年代中的根对象A,有成员变量,指向了年轻代中一个对象B,此时B对象无法被Young区中的根对象找到,为了将B也正确标记为可达,就需要遍历所有Old区中的对象,看是否有指向B的引用,为了避免这种情况的产生,引入了			   RSet
6. RSet:是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构,Card Table是G1和CMS对RSet的一种具体实现
7. Card Table:将堆空间划分为一系列2的n次幂大小的空间,称为card(卡页),对于HotSpot JVM,卡页大小为512字节,将所有卡页的起始地址右移9位,得到的数值,作为一个byte[]的索引,这个byte[]就是所谓的Card Table(卡表),也就是根据byte[]的索引就能找到指定的卡页,同时该byte[]中的元素值,代表对应的卡页的状态
8. CSet:Collection Set,表示本次要被收集的Card的集合
9. CMS处理跨代引用时对Card Table的使用
	1. 这个Card Table的索引,包含所有老年代中的卡页
	2. 老年代中的某个卡页中的对象,指向了年轻代中的对象,就将以该卡页的地址作为索引的byte[]处元素值,设置为0表示dirty
	3. YGC时,只需要顺着Young区所有Root对象进行扫描,并加上顺着dirty的card中的引用进行扫描即可找到所有Young区的可达对象
10. G1处理跨代(Region)引用时对Card Table的使用
	1. 在上面介绍的Card Table使用方法的基础上,加入了一个存放于当前Region的Card Table
	2. 这个Card Table包含所有指向存放它的Region的其他Region中的卡页
	3. 这样当想扫描Region时,对于跨代的引用,只需要扫描Region中的RSet所代表的卡页中,是否有可达对象指向自身即可
	4. RSet会需要堆的10%-20%的内存用来维持垃圾收集器工作
11. 可达性分析算法:三色标记
	1.	难点:标记对象过程中,对象引用关系正在发生改变
	2.	做法
		白色:尚未被标记,最后白色对象被回收
		灰色:自身被标记,成员变量指向的对象未标记
		黑色:自身和成员变量指向的对象均标记完成
	3. 漏标
		1. 工作线程将B到D的引用切断,由A中成员引用D
		2. 此时由于垃圾回收线程已经扫描过A,不会再对其成员引用的D对象进行标记,而扫描B的属性时,又无法通过该属性的引用找到D
		3. 此时D无法正确被标记为可达对象


	4. 解决方案
		1. 跟踪A指向D的增加(incremental update):CMS引入了另一个数据结构mod union table,这里一个bit对应一个Card,young gc在将Card Table设置为clean的时候会将对应的mod union table置为dirty,重新标记的时候会将Card Table或者mod union table是dirty的Card也作为root去扫描
		2. 跟踪B指向D的消失(SATB,snapshot at the beginning  原始快照):将B到D的引用放入一个特殊的栈中,重新标记时,会继续对栈中的对象进行标记
12. G1的新生代大小默认占堆的5%-60%,收集过程目标时间默认为200ms,不要手工指定G1的年轻代和老年代的大小,由于G1会造成STW,所以G1会根据收集过程目标时间,自动优化新生代大小,比如本次YGC较慢,下一次就会将年轻代比例调小
13. G1的GC模式
	1. young gc:stw
		1.	eden region被耗尽无法申请内存时触发
		2. 活跃对象会被拷贝到survivor region或者晋升到old region中
	2. mixed gc
		1. 由-XX:InitiatingHeapOccupancyPercent参数设定,默认45%,即当老年代占整个堆的45%以上时触发mixed gc
		2. 过程相当于一套完整的CMS
			1. 初始标记
			2. 并发标记
			3. 最终标记
			4. 筛选回收:与CMS不同
				1. 会多了筛选的步骤,优先选择垃圾最多的Region进行回收
				2. 会将Region中存活的对象复制到空的Region中,并清理旧的Region空间
				3. 由于涉及存货对象的移动,因此无法与用户线程并行执行
		3. full gc
			1. 对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满时触发
			2. G1的full gc,在java10以前为单线程的Serial Old,		java10以后才是并行回收,效率非常低
			3. 应尽量避免full gc
				1. 扩内存
				2. 提高cpu性能(加快回收速度)
				3. 降低mixed gc的阈值,让mixed gc提早发生

参考资料:
https://blog.csdn.net/hanzong110/article/details/104693331
https://blog.csdn.net/u010889616/article/details/80024454

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值