GC垃圾回收机制

垃圾回收机制干嘛的

当我们运行一个程序时,创建的对象和一些变量是存在内存中,如果我们创建的对象和变量过多,它会占用大量的内存,在程序运行时,有一些对象和变量可能是无用的,我们没必要浪费内存去存储,垃圾回收机制就是帮我们回收这些无用的垃圾。

垃圾回收机制主要针对哪些内存

JVM运行时内存区域主要包括五大部分,分别是程序计数器,本地方法栈,java虚拟栈,方法区,堆,由于,程序计数器,本地方法栈,java虚拟栈是每个线程私有的,当线程运行完毕时,会自动回收这些内存区域,而对于堆和方法区是线程共享的,这部分内存的分配和回收都是动态的,所以方法区和堆是GC主要针对的区域。堆中需要回收的主要是对象,方法区回收的主要是一些无用的常量和类,无用的常量可以通过引用计数可以判断是否是废弃的常量。无用的类是指该类的实例都已经被回收,加载该类的ClassLoader已经被回收,该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类

GC搜索算法

引用计数器:
每一个对象都拥有一个对象引用计数器,当增加一个对该对象的引用时,引用计数器就会加一,减少一个对象引用,引用计数器就会减一,当该对象的引用计数器为0时,则认为该对象没有被引用是可以进行回收的,但是引用计数器有一个缺点,就是无法解决循环引用,比如A对象引用了B对象,B对象引用了A对象,但是这两个对象没有被任何的其它对象引用,这两个对象就无法回收了

GC Roots可达性分析

从一些GC ROOTS对象作为起点,向下搜索,搜索通过的路径为引用链,当一个对象没有被该引用链连接时,则认为该对象是无用的。

GC Roots对象包括虚拟机栈中的引用的对象,方法区域中的类静态属性引用的对象,方法区域中常量引用的对象。

对象的引用是什么

无论是引用计数器还是可达性分析,判断对象是否有用都与引用有关,那如何定义对象的引用。

Java中对象的引用分为四种级别,由高到低分别为:强引用,软引用,弱引用,虚引用

  • 强引用(Strong Reference):

强引用在我们每天写代码的时候都会用到,比如Object obj = new Object();如果一个对象被强引用引用,那么这个对象是不会被垃圾回收器回收的。

  • 软引用(SoftReference):

如果一个对象具有软引用,当JVM内存空间充足的情况下,垃圾回收器不会回收它
示例:配置jvm运行参数 -Xms5m -Xmx5m

		Object obj = new Object();
		// 软引用
		SoftReference<Object> sr = new SoftReference<>(obj);
		try {
			System.out.println(obj);//java.lang.Object@15db9742
			System.out.println(sr.get());//java.lang.Object@15db9742
			obj = null;
			// 创建超出最大堆内存的对象
			byte []bs = new byte[1024*1024*10];
		} catch (Exception e) {
		}finally {
			System.out.println(obj); //null 
			System.out.println(sr.get());//null 堆内存不够,清理软引用
		}

可以用来实现内存敏感的高速缓存.在jvm报告内存不足时,立刻清空所有软引用

gc回收软引用的过程

  1. 首先将SoftReference引用的obj置空
  2. 标记new Object()为finalize
  3. 回收内存,并添加到RefererceQueue.(如果有的话)
  • 弱引用

被弱引用引用的对象可有可无,只要被GC扫描到,随时都会被清除。

	Object obj = new Object();
	WeakReference<Object> wr = new WeakReference<Object>(obj);
	System.out.println(obj);//java.lang.Object@15db9742
	System.out.println(wr.get());//java.lang.Object@15db9742
	obj = null;
	System.gc();
	System.out.println(obj); //null
	System.out.println(wr.get());//null 弱引用 gc回收垃圾时回收
  • 虚引用
    如果一个对象持有虚引用,那么它就与没有任何引用一样,在任何时候都会被垃圾回收器回收,不能单独使用也不能通过它访问任何对象,虚引用必须和引用队列(ReferenceQueue)联合使用 。

      Object obj = new Object();
      ReferenceQueue<Object> queue = new ReferenceQueue<>();
      PhantomReference<Object> pr = new PhantomReference<Object>(obj,queue);
      System.out.println(obj);//java.lang.Object@15db9742
      System.out.println(pr.get());//null
      System.out.println(queue.poll());//null
      //=================================
      obj = null;
      System.gc();
      System.out.println(obj);//null
      System.out.println(pr.get());//null
      System.out.println(queue.poll());//java.lang.ref.PhantomReference@6d06d69c
    

GC回收算法

  • 标记清除法:标记清除法从GC ROOT出发,进行扫描,对存活的对象进行标记,标记完后,再扫描整个空间未被标记的对象,进行回收。标记清除法容易产生大量不连续的空间。
  • 标记整理法:采用与标记清除法一样的方式对对象进行标记,它会将存活的对象移到一端,然后将边界的对象清除,解决了内存碎片的问题。
  • 复制算法:把堆分成大小相同的两块,每次使用一块,将存活的对象移动到空闲的一块,然后将另一块的内存清空。由于需要分配空闲的内存,所以内存利用率不高
  • 分代收集算法:大部分jvm目前采用的算法,它将根据对象存活的生命周期将内存分为若干不同的区域。一般分为老年代,新生代,在堆区之外还分配一个永久代。老年代的特点是每次垃圾回收只有少量的对象需要被回收,一般采用标记整理法,新生代由于每次都有大量的对象需要被回收,一般采用复制算法。

新生代内存按照8:1:1的比例分为一个eden区,和两个survivor区(survivor0,survivor1),所有新生成的对象首先会被放在年轻代,回收时先将一个eden区的存活对象复制到一个survivor0区,然后清空eden区,当survivor0被放满了,会将eden区和survivor0区的对象复制到survivor1,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0与survivor1交换,保证survivor1是空的,如此循环往复。但是当survivor1不足以存放eden区和survivor0区的对象时,就将存活对象存放到老年代,若是老年代也存满了则触发Full GC,就是新生代老年代都进行回收。当新对象在Eden区申请空间失败时,会触发Eden区的垃圾回收。(Scavenge GC)

在年轻代存活了N次垃圾回收后任然存活的对象,就会被存放到老年代,当老年代内存满的时候会触发Full GC.(对整个堆进行垃圾回收)

垃圾回收器

在这里插入图片描述

Serial 收集器

单线程的收集器,进行垃圾回收时,必须暂停所有工作线程,直到它收集结束(stop the world)

新生代使用复制算法,老年代使用标记整理算法

ParNew收集器

并行垃圾回收器,Serial的多线程版本,除了Serial收集器,目前只有它能与CMS收集器配合工作

新生代使用复制算法,老年代使用标记整理算法

Parallel Scavenge

一种新生代收集器,同样使用复制算法的并行收集器。ParNew收集器的关注点与其它收集器不同,他的关注点在吞吐量,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾搜集时间)。

Serial Old收集器

是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记整理算法,有两种用法,一种用法是与Parallel Scanvage使用,另一种是作为CMS的后配方案。

ParallelOld

Parallel Scnvenge的老年代版本。

CMS收集器

一种以获取最短停顿时间为目标的收集器。采用并发清除的方式对垃圾进行回收,主要有四个步骤

  • 初始标记:对GC ROOT能直接关联到的对象进行标记
  • 并发标记: 根据可达性分析对对象进行标记
  • 重新标记:用户线程执行过程中会导致标记产生变动,需要重新标记
  • 并发清除

初始标记和并发清除两个步骤任然需要stop the world,整个过程耗时最长的并发标记和并发清除可以和用户线程一起工作

主要特点:并发收集,低停顿

缺点:

1.虽然不会导致用户线程停顿,但是会因为占用了一部分线程导致应用程序变慢,系统吞吐率降低
2.无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行这,伴随着程序运行自然会有新的垃圾产生,这一部分垃圾,CMS无法在本次收集中处理他们,只好留待下一次GC时再清理,这一部分就称为浮动垃圾,也是由于在垃圾收集阶段用户线程还需要运行,需要留有足够的内存空间给用户线程使用,因此CMS收集器不能像其它收集器等到老年代几乎填满了再进行收集,需要预留一部分空间提供并发收集时的程序使用,如果预留的空间太少。会出现Concurrent Mode Failure失败,这时候会临时启用Serial Old 收集器来重新进行来年代的垃圾收集
3.由于CMS采用的是标记清除算法,所以在进行垃圾回收后会产生大量的内存碎片
G1收集器

将整个Java堆规划分为多个大小相等的独立区域,保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的,是一部分Region的集合

回收步骤:

  • 初始标记:对GC ROOT能直接关联到的对象进行标记
  • 并发标记:根据可达性分析对对象进行标记
  • 最终标记:用户线程执行过程中会导致标记产生变动,需要重新标记
  • 筛选回收:对各个region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

与CMS相比:

  • 从整体来看是采用标记整理算法实现的收集器,从局部开看是使用的复制算法实现的,但是这两种算法都不会产生内存空间碎片
  • 可预测的停顿,G1可以建立可预测的停顿时间,能让使用者明确指定在一个时间片段内,消耗在垃圾收集上的事件不得超过N毫秒

ps

并行和并发:
并行:在同一时刻多个垃圾回收线程同时执行,用户线程处于等待状态
并发:用户线程与垃圾收集线程同时工作(不一定是并性的,可能会交替执行),用户线程继续运行,而垃圾收集程序运行于另一个CPU上

在这里插入图片描述
我的博客:GC垃圾回收机制

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值