JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

一、如何判断对象为垃圾


1)引用计数算法(已淘汰):

原理:
	对象中存在一个引用计数器
	对象被引用时,计数器+1; 引用失效时,计数器-1
	计数器为0时,对象被回收
淘汰原因:无法解决对象间相互引用的问题。
	当外界不访问A,B两个对象,且对象A,B相互引用时,计数器不为0,则无法回收

2)可达性分析算法:

思想:
	定义GC Root作为引用链起点,向下搜索。
	被搜索到的节点对象判定为活
	搜不到的节点对象将被回收

可作为GC Root的对象
	1)虚拟机栈中局部常量表中对象
	2)方法区中的类静态属性所引用的对象
	3)方法区中的常量所引用的对象
	4)本地方法栈中所引用的对象
	
一个对象的死亡判定需经过两次标记:
1)第一次标记:
	可达性分析后,发现该对象没有与GCRoot相连。进行第一次标记与筛选
	筛选条件:对象是否有必要执行finalize()方法  
		判定没有必要执行的两种情况:
			1)对象没有覆盖finalize()方法
			2)finalize()方法已经被调用过。
2)第二次标记:
	对于重写了且并没有执行finalize()方法的对象,将其放置在一个F-Queue队列中,并在稍后由一个由虚拟机自动建立的低优先级的Finalizer线程去执行它。
	此处执行只保证执行该方法,但是不保证等待该方法执行结束。
	这样子设计是为了系统的稳定性和健壮性考虑,以免该方法执行时间较长或者死循环导致系统崩溃。
	在此之后,系统会对对象进行第二次标记。
	如果在第一次标记之后的对象在执行finalize()方法时没有被引用到一个新的变量,这该对象将被回收掉。

二、回收策略


1)标记-清除算法

算法思想:先标记需要清除的对象(使用可达性分析判断),后清除
缺点:
	1)效率问题:标记与清除两个过程效率都不高
	2)空间问题: 清除后会产生大量不连续的内存碎片

JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

2)复制算法

算法思想:
	1)将堆区域分为两部分,每次只使用其中一块。 
	2)当被使用的内存用完时,将内存中仍存活的对象复制并排列到另一块区域中,
	3)一次清空使用过的内存
实际应用:
	
缺点:内存闲置大

注:堆区域的实际划分:Eden和Survivor的大小比例是8:1。
原因:新生代中的对象98%是“朝生夕死”的。

JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

3)标记-整理算法

算法思想:
	1)在内存中划出界限,
	2)存活对象往界限一端移动,可回收对象往另一端移动
	3)清空界限的一端(被回收的一端)

JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

4)分代收集算法

思想:
1)根据对象不同年代实施不同回收算法
2)新生代采用复制算法
3)老年代采用 标记-清除/标记-整理算法

三、垃圾回收器


1)Serial收集器

单线程收集器(整体性能低,单个效率高)
多用于桌面应用场景中

JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

2)ParNew收集器

多线程
缩短停顿时间

JVM---如何判断对象为垃圾、回收策略、收集器、内存分配、引用

3)Parallel Scavenge收集器

目标:达到一个可控制的吞吐量(Throughput)

吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。

停顿时间越短对于需要与用户交互的程序来说越好,良好的响应速度能提升用户的体验;

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

4)Cms收集器

一种以获取最短回收停顿时间为目标的收集器
CMS收集器是基于“标记-清除”算法实现的。它的运作过程相对前面几种收集器来说更复杂一些,整个过程分为4个步骤:
	1)初始标记  --Stop The World
	2)并发标记
	3)重新标记  --Stop The World
	4)并发清除
	
优点:并发收集,低停顿。
缺点:
	1)CMS收集器对CPU资源非常敏感
	2)CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
	3)CMS是基于“标记-清除”算法实现的收集器,收集结束时会有大量空间碎片产生。

5)G1收集器

步骤:
	1)初始标记
	2)并发标记
	3)最终标记
	4)筛选回收
优点:	
	1)并行与并发
	2)分代收集
	3)空间整理 (标记整理算法,复制算法)
	4)可预测的停顿

四、内存分配


对象的内存分配:分配堆内存

优先策略:
	1)启动本地线程分配缓冲时,优先TLAB
	
	2)优先Eden
	
	3)大对象直接进入老年代  大对象:需要大量连续内存空间的Java对象
	
	4)长期存活的对象将进入老年代
	
	5)空间分配担保
		检查老年代最大可用空间是否可以容纳新生代所有对象(防止新生代全部晋升时放不下)。
			可以容纳,则MinorGC可以安全执行。
			不能容纳,检查是否允许担保失败。
				允许则检查 老年代最大可用空间 是否大于 历次晋升到老年代的对象的平均大小。
					是则尝试进行MinorGC;
					小于或者MinorGC失败,则会发起一次FullGC清理老年代。

栈上分配:

在方法体中声明的变量以及创建的对象,从该线程所使用的栈中分配空间。

逃逸:

指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到。
在该方法执行完毕之后,由于其被其它变量引用,该对象将无法被GC回收。
这种情况被称为逃逸

逃逸分析: 分析对象的作用域

当作用域为方法体内时,则认为没有发生逃逸,则可被回收···········

四种引用:

1)强引用:类似Object obj = new Object()的引用。强引用的对象永远不会被回收 
	
2)软引用:描述一些有用但非必需的对象。 在发生内存溢出异常之前,会把这些对象列进回收范围进行第二次回收

3)弱引用:被引用对象只能生存到下一次垃圾收集之前。 无论内存是否足够,都会被回收

4)虚引用:唯一作用:在对象被回收之前收到一个系统通知