Java垃圾回收机制(GC)

x33g5p2x  于2021-08-22 转载在 Java  
字(2.4k)|赞(0)|评价(0)|浏览(289)

如何识别出当前对象是否是垃圾

垃圾:也就是这个对象以后没人使用(对于这个代码起不到贡献了)

引用计数(在Java中实际上没用到)

就比如有一个对象,创建对象的同时,给这个对象搭配一个计数器;

每次有一个引用指向这个对象,计数器就 +1,每次有一个引用被销毁那么计数器就 -1 ;

当计数器为0的时候,这个对象也就没人引用了,自然也就成了垃圾;

举个例子:

class Test{
 void test(){
	Test t = new Test();
	Test t2 = t;
 }
}

在上述代码中,当执行到第一个代码 Test t = new Test(); 此时计数器 ,然后Test t2 = t; 此时t2也指向了Test(); 计数器再次;此时计数器的值就为 2,表示当前有两个引用指向这个对象;

当test()方法结束后,两个引用就都会被销毁,计数器就为0了,也就可以认为Test是一个垃圾;

引用计数是一个简单直观的衡量对象是否是垃圾的办法,好处就是内存回收的更加及时;

但是也有缺陷;

首先不难看出,就需要要求计数器 ++ 或者 减减 的操作是线程安全的;

也就是说如果是多个线程下,同时针对同一个对象进行引用赋值,此时就需要加锁,但是加锁也就意味着低效。
1.
可能也会出现循环引用的问题;

public class Test{
	static class T{
		public T t = null;
	}
   public static void main(String[] args){
   		T t1 = new T();
   		T t2 = new T();
   		t1.t = t2;
   		t2.t = t1;
   }
}

此时执行代码的时候,new了两个对象,并且 t1.t = t2 ; t2.t = t1;所以每一个对象有获得了一个引用,此时,他们的计数器值都为2;

方法结束后 t1 和 t2 被销毁了,但是 t1.t 和 t2.t 并没有销毁,因为 t 这个属性是在堆上的,此时引用计数器仍然是 1 ,但是却没有办法获取到这两个对象了;

换句话说,也就是这两个对象相互之间持有对方的引用,但是有谁都访问不到谁;

举个例子:

现在有两个犯罪嫌疑人 A 和 B ,怀疑他们犯罪了;
警察问:A,你有不在场证明吗? A说,B可以证明我不在场;
警察又问:B,你有不在场证明吗? B说,A可以证明我不在场;

此时没有其他人能够证明 A 和 B 不在场;
警察就会怀疑他们是同伙犯罪;

除非有一个好人 C 能证明 A 或 B 不在场,如果不存在 C 那么 A 和 B 无法证明自己的清白!

所以在代码中,这两个对象应该是垃圾,但是这两对象互相证明自己不是垃圾,导致计数器就不为 0,就是就无法被回收;

可达性分析

Java中的对象都是通过引用来获取到的,一个引用能指向一个对象,一个对象里可能包含多个引用,这些引用关系,就构成了一个“图状结构”。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可达性分析,从GCRoots出发,能够被访问到对象就是“可达的”,不能被访问到的对象就是“不可达的”。

在JVM中有一个专门的线程,定期的扫描对象之间的引用关系,识别那些对象已经遍历不到了~

GCRoot:也就是从哪里开始遍历

  • 栈上的局部变量表中的引用;
  • 常量池中的引用指向的对象;
  • 方法区中的静态的引用类型的属性。

这三个部分都是代码中直接使用的,这三个肯定是可达的,通过这些可达的对象开始往下找,能找到的对象都是可达的。

确定垃圾后,如何会收垃圾

实际的JVM在GC的时候,不只是单单使用一种算法,会多种算法结合使用!

标记清除

1.先标记出垃圾
2.在清除垃圾

标记的方式就是可达性分析,可达的对象除外,剩下不可达的就标记为垃圾,然后清除就是释放对象的内存空间。

优点:简单,容易实现!
缺点:可能会产生很多的内存碎片!!

在这里插入图片描述

清除后:

在这里插入图片描述

但是在代码中经常会涉及到,申请一个连续的内存空间;

也就是说内存空间总共是100M,但是是由100个1M大小的碎片构成的,所以就会导致,你想申请一个
2M的内存但是申请不到~

但是在JVM和操作系统,帮我们把内存碎片已经处理了!

复制算法

能够解决内存碎片问题~

1.将一个完整的内存分为2分,只用其中的一份;
2.遇到要回收的垃圾,将不是垃圾的对象复制到另一份内存中,然后回收掉整个存在垃圾的内存;

在这里插入图片描述

在这里插入图片描述

优点:解决了内存碎片问题

缺点:内存利用率不高,要切除一半;
有局限性,如果复制的对象多,垃圾少,可能会低效

标记整理

采用类似于“顺序表”删除元素的方式,搬运内存

在这里插入图片描述

在这里插入图片描述

优点:没有内存碎片了,空间利用率高

缺点:内存搬运操作比较繁琐,效率低

分代回收

把回收的过程分成了几个场景,不同的场景下采用不同的回收方式!

这里介绍一个典型的分代方式:

分代回收,主要基于一个经验规律:如果认为一个对象存活的时间越久,就认为这个对象会继续更久的存活下去;

衡量对象的“存活时间”:根据这个对象躲过GC的轮次,(JVM会周期性的进行可达性分析)

在这里插入图片描述

1.新的对象在伊甸区上分配;

2.但是有一个经验规律,在伊甸区的新对象,大部分都活不过一轮GC;

3.熬过一轮GC的对象,就进入了“幸存区”

4.幸存区里的对象,也会经受GC的考验,两个幸存区相互配合,使用“复制算法”,每次经过GC考验的对象,就被拷贝到另一个幸存区,反复这个过程;

5.在“幸存区”经历了多次GC之后,年龄积累到一定程度,对象就会从幸存区拷贝到了老年代;

6.老年代的对象也不是一直不回收,而是回收的频率降低了。

在分代回收中,根据对象的年龄来预测生命周期是长还是短,在伊甸区和幸存区经历一定的GC,才能证明自己能去老年代~

新生代的扫描概率较高(新生代的对象大概率是垃圾)
老年代的扫描频率较低(老年代成为垃圾的概率也低)

还有一个特例: 如果是一个很大的对象,就不适合在幸存区复制过来,复制过去的,会很大的降低效率,所以就可以直接进入老年代~

相关术语

Particial GC :进行部分内存区域的垃圾回收
Full GC :针对全部内存进行回收
Minor GC :针对部分内存区域的垃圾回收(一般是指针对新生代)
Major GC :进行大部分内存去与的回收(一般是指针对老年代和新生代)

相关文章