垃圾:也就是这个对象以后没人使用(对于这个代码起不到贡献了)
就比如有一个对象,创建对象的同时,给这个对象搭配一个计数器;
每次有一个引用指向这个对象,计数器就 +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 :进行大部分内存去与的回收(一般是指针对老年代和新生代)
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/wlm123666/article/details/119813164
内容来源于网络,如有侵权,请联系作者删除!