JVM垃圾回收机制

目录

 

概述

什么是垃圾?

为什么要回收?

内存溢出

内存泄露

垃圾回收算法

标记阶段算法

引用计数算法

根可达算法(可达性分析算法)

finalize()

对象回收细节

垃圾回收阶段算法

复制算法

标记-清除算法

标记-压缩算法(标记-清除-压缩算法)

分代收集

STW(Stop The World)

垃圾回收器

垃圾回收器的分类

GC 性能指标

HotSpot 垃圾收集器

CMS(Concurrent MArk Sweep,并发标记清除)

三色标记算法

G1(Garbage First 垃圾优先)


概述

       java语言是提供自动垃圾回收功能的,C++没有自动垃圾回收,垃圾回收也不是java首创的,java在垃圾回收这块一直在不断更新升级。

什么是垃圾?

没有被任何引用指向的对象。

哪些区域回收?

  1. 方法区:基本不回收方法区
  2. 堆:频繁回收新生代,较少回收老年代

例:

new User().toString();

Object obj = new Object();

            obj.hashCode();

            obj = null;

为什么要回收?

    垃圾对象如果不回收,它就会一直占用内存空间,垃圾对象越积越多,从而可能导致内存溢出,堆内存空间中的碎片进行管理,如果不整理,需要存储象数组这样的对象下。

早期的垃圾是怎么进行回收?

早期像C++,需要程序员手动销毁垃圾对象

不足:麻烦,程序员手动删除,有时候可能会忘记删除,导致内存溢出

内存溢出

内存不够用,报内存溢出错误

内存泄露

       有些对象已经不再被使用了,但是垃圾回收对象又不能回收它,这种悄悄的占用内存资源的现象称为内存泄露。

现在的java语言引进自动的内存管理

优点:降低了程序员的工作量,降低了内存溢出和内存泄露的风险

自动内存管理的担忧

     自动的垃圾回收,降低了程序员对内存管理的能力,一旦出现问题,无法下手解决。

垃圾回收算法

标记阶段算法

标记哪些对象已经是垃圾

引用计数算法

Object obj = new Object(); 对象内部有一个计数器,有一个引用指向计数器+1
obj.hashCode(); 
Object object = obj; 又有一个引用指向对象,计数器 + 1
obj = null; 计数器 - 1
object = null; 计数器 -1

优点:实现简单

缺点:不能解决循环引用问题,需要维护计数器空间,赋值后对计数器进行更新操作

根可达算法(可达性分析算法)

 List<Integer> list = new ArrayList();
 while(true){
     list.add(new Random().nextInt());
 }

 实现思路:
    从一组GCRoots对象(一组活跃的对象,当前栈桢中使用的对象)开始向下查找,如果与GCRoots对象相关联的,那么就不是垃圾对象,否则判定为垃圾对象。

哪些元素可以作为GCRoots对象?

  • 栈桢锁使用的对象(虚拟机栈、本地方法栈)
  • 静态成员变量所引用的对象
  • synchronized 同步锁对象
  • JVM系统内部的对象

优点:可以避免对象循环引用的问题

finalize()

public class CanReliveObj {

	public static CanReliveObj obj;//类变量,属于 GC Root
	//此方法只能被调用一次
	 @Override
	protected void finalize() throws Throwable {
		//super.finalize();
		System.out.println("调用当前类重写的finalize()方法");
		obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
	}
	public static void main(String[] args) {
		try {
			obj = new CanReliveObj();
			// 对象第一次成功拯救自己
			obj = null;
			System.gc();//调用垃圾回收器,触发FULL GC  也不是调用后立刻就回收的,因为线程的执行权在操作系统
			System.out.println("第1次 gc");
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}

			System.out.println("第2次 gc");
			// 下面这段代码与上面的完全相同,但是这次自救却失败了
			 obj= null;
			System.gc();
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 执行结果:

    finalize() 是Object 类中定义的方法,子类可以重写,但是不要主动自己去调用它,finalize()在对象回收前,有垃圾回收线程调用,只被调用一次。一般情况下,不需要重写此方法,如果在对象销毁前需要执行一些释放资源的操作,可以重写此方法,但是要注意,不要在此方法将对象复活或出现死循环

生存还是死亡?

从垃圾回收的角度,对象可分为三种状态:

可触及的:从根节点开始,可以到达这个对象

可复活的:已经被标记为垃圾,finalize()方法还没有执行(对象有可能执行finalize()) 

不可触及的:当对象finalize()已经执行了,而且对象没有被复活,那么对象就进入到不可触及状态。

对象回收细节

     如果一个对象第一次被标记为垃圾,且finalize()没有执行,将这些对象放到一个队列中,调用他们的finalize(),如果在finalize()方法,对象与GCRoots中的某个对象关联上了,从队列中移出。当第二次对象被标记为垃圾对象,那么直接就是不可触及状态,被回收掉。

final、finally、finalize的区别
final
:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。

finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。

垃圾回收阶段算法

复制算法

复制:使用两块内存空间(标引为两个新生代),将当前空间的存活的对象复制到另一个空间中,整理排放整齐,清除原来的空间。

优点:内存的碎片少

缺点:需要的内存大,因为需要两块内存空间。G1垃圾回收器每个区域又分为多个小的区域,需要记录地址(时空开销较大)

复制算法

标记-清除算法

清除:将根可达算法得到的垃圾对象进行标记而不是清除,将标记的垃圾对象的地址记录放入到一个空白的列表,当需要创建一个新的对象需要分配内存空间时,会先从空闲列表判断空间是否够用,如果够用,则将创建的对象替换掉垃圾对象。

优点:实现简单,不需要移动对象

缺点:内存碎片较多

标记-清除算法

复制与标记清除算法的区别

复制算法针对新生代,对象较少的情况,需要移动对象,效率高,不会产生内存碎片,但是对于老年代,对象较多的情况不使用。

标记-清除算法是针对于老年代存活的对象较多,不需要移动对象,但是不会整理对象,会产生内存对象。

标记-压缩算法(标记-清除-压缩算法)

为了解决标记清除算法的不足,(内存碎片太多)

将存活的对象进行整理,然后清除垃圾对象,这样就不会产生内存碎片。

优点:不会产生内存碎片,节省了内存空间(相比于复制算法)

缺点:从效率上看,要低于标记-清除算法,移动的时候需要暂停用户线程。

标记-压缩算法

标记清除算法与标记压缩算法的区别

  • 两者都要标记垃圾
  • 标记清除不移动对象,产生内存碎片,将垃圾对象维护到一个空闲的列表中
  • ​​​​​​标记压缩移动对象,不会产生内存碎片,清理掉对象并进行整理压缩。
标记清除标记整理复制
速率中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要存活对象的2倍空间(不堆积对象)
移动对象

分代收集

年轻代(新生代):存活的对象生命周期短,需要频率回收,复制算法效率高,适合用于年轻代。

老年代:对象生命周期较长,不需要频率回收,使用标记清除和标记压缩算法。

STW(Stop The World)

    在垃圾回收线程标记时,需要在某个时间点上,让所有的用户线程暂停一下,保证在判定对象是否垃圾时的准确性。性能好的垃圾回收器,发生STW次数少。

垃圾回收器

垃圾回收器是对垃圾回收的落地实现。

JVM中分为不同种类的垃圾回收器,可以根据场景选择对应的垃圾回收器。

垃圾回收器的分类

按照线程数量分

单线程垃圾回收器(Serial):垃圾回收线程只有一个,适用于小型的场景。


多线程垃圾回收器(Parallel):垃圾回收线程有多个同时执行,执行效率高。

 

按照工作模式分

独占式:垃圾回收线程工作时,用户线程全部暂停。例:STW

并发式:垃圾回收线程执行时可以不用暂停用户线程,从CMS这款垃圾回收开始引入并发执行。

按照内存的工作区域分

年轻代区域的垃圾回收器和老年代区域的垃圾回收器

GC 性能指标

  • 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运 行时间+内存回收的时间)
  • 垃圾收集开销:垃圾收集所用时间与总运行时间的比例。
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
  • 内存占用:Java 堆区所占的内存大小。
  • 快速:一个对象从诞生到被回收所经历的时间。

HotSpot 垃圾收集器

       图中展示了 7 种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
  • 串行回收器:Serial、Serial Old
  • 并行回收器:Parallel Scavenge、Parallel Old、ParNew
  • 并发回收器:CMS、G1

CMS(Concurrent MArk Sweep,并发标记清除)

      CMS之前不管是单线程还是多线程的垃圾回收器,都是独占的并发标记,清除首创用户线程可以和垃圾回收线程并发执行。

目的:追求低停顿。 

垃圾回收过程

1. 初始化标记:单线程独占标记对象(STW)单线程

2. 并发标记:垃圾回收线程和用户线程并发执行   多线程

3. 重新标记:使用多线程独占进行标记对象   独占的

4. 并发清除:垃圾回收线程和用户线程并发执行

优点:可以做到并发收集

缺点:

  • 用户线程和垃圾回收并发执行,导致吞吐量降低。
  • 无法处理浮动垃圾

浮动垃圾:垃圾回收线程并发标记时,用户线程不暂停,标记完成后,随时会产生新的垃圾,无法处理,只能等到下次垃圾回收处理。

三色标记算法

将对象分为不同的状态:黑色、灰色、白色

黑色:已经被标记过的不是垃圾的对象,且该对象下的关联属性也标记过了。

灰色:已经被垃圾回收器扫描过,但是还存在没有标记过的

白色:没有被垃圾回收器扫描过的,表示不可达(标记为垃圾)

步骤:

  1. 刚开始,确定为GC Roots的对象是黑色
  2. 将GC Roots对象直接关联的对象置为灰色
  3. 遍历灰色对象,下面如果还有关联的对象,灰色变为黑色,下面关联的是灰色
  4. 重复标记
  5. 将白色的对象清除

问题:会出现漏标错标的问题

漏标:A关联B,B关联D,当B是灰色,此时,A和B断开了联系,但是B已经是灰色的,B和D就是浮动垃圾,需要等待下次回收。

错标:A关联B,B是灰色,B和D断开,A和D建立连接,A是黑色的,不会再次扫描,就会将D清理掉(出现程序错误)

解决错标问题:

将发生变化的关系进行记录,重新标记。

G1(Garbage First 垃圾优先

 G1也是使用并发标记和清除的

      将整个堆的每个区域划分为更小的区间,回收时可以根据每个区间的优先级(由里面的垃圾量),先回收优先级较高的空间,降低了用户线程的停顿,提高了吞吐量。对整堆进行统一的管理,没有年轻代和老年代。

       因为 G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region) (物理上不连续的逻辑上连续的)。使用不同的 Region 来表示 Eden、幸存者 0 区,幸存者 1 区,老年代等。 G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

 流程:

前三个步骤与CMS一样

  1. 初始化标记:单线程独占标记对象(STW)单线程
  2. 并发标记:垃圾回收线程和用户线程并发执行   多线程
  3. 最终标记:使用多线程独占进行标记对象   独占的(修改并标记因应用程序变动的内容(对象依赖关系发生变化)STW)
  4. 筛选回收:对各个 Region 的回收价值和成本进行排序,回收价值最大的 Region(用最少的时间来回收包含垃圾最多的区域)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值