4、JVM垃圾回收机制

为什么要有垃圾回收机制?

因为会有垃圾产生啊,可垃圾什么时候会产生呢?这就是涉及到运行时数据区的工作流程了:

当线程A在执行如下代码时:

public void say(){
    A a = new A();
}

此时对应于JVM运行时数据区发生的事情便是:a对象的引用放在了当前线程栈中的say()方法对应的栈帧中的局部变量表中,然后a对象本身放在了堆内存中:

当say()方法执行完毕返回之后,此时say()方法对应的栈帧便从当前线程的栈中出栈了,栈帧就消失了,栈帧中的局部变脸表当然也会消失掉,此时堆内存中的那个a对象本身就没有人引用了,成为了垃圾:

如果不对这些垃圾进行回收,堆内存大小是有限的,迟早会发生内存溢出。所以JVM后台有垃圾回收线程,不断地去扫描,判断哪些是垃圾对象,然后进行垃圾回收。

垃圾回收是针对JVM中哪些内存区域?

JVM内存区域,也可以叫运行时数据区,分为程序计数器、虚拟机栈、本地方法栈、方法区、堆内存。垃圾回收只针对方法区和堆内存进行垃圾回收。

其中方法区(元数据区)又叫永久代,而堆内存在进行垃圾回收时,被划分为新生代与老年代。

那到底什么是垃圾呢?垃圾回收器是如何判定一个对象是或不是垃圾呢?

判断什么是垃圾就是所谓的垃圾判定算法,常用的有两种:引用计数法,可达性分析法。

引用计数法:

给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收。缺点:无法解决循环引用的问题:

如下图所示,A、B、C三个对象相互引用,所以A、B、C三个对象里面的引用计数器都不是0,所以不会被回收,但是程序始终使用不到这三个对象了,因为这三个对象的引用并不来自于GC Roots。

可达性分析法:

通过GC ROOT的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收。通过可达性算法,成功解决了引用计数所无法解决的循环引用问题,只要你无法与GC Root建立直接或间接的连接,系统就会判定你为可回收对象:

实际JVM中采用的垃圾判定算法都是可达性分析法。

那么具体哪些属于GC ROOT呢?

虚拟机栈中引用的对象:也就是方法里面的局部变量。

方法区中类的静态变量引用的对象:也就是类的静态变量。

方法区中常量引用的对象:被final修饰的常量。

本地方法栈中引用的对象:

Java中对象的不同引用类型对垃圾回收作何反应?

强引用:

Object obj = new Object();

在这段代码中,就产生了一个obj对new Object()创建的那个对象的强引用。被强引用的对象是绝对不会被垃圾回收的。

软引用:

Object obj = new Object();

SoftReference<Object> sf = new SoftReference<Object>(obj);

Obj = null;

在这段代码中sf对new Object()创建的对象便是一个软引用,通过sf.get()便可以获得obj,但有时获取到的是null。 只有软引用的对象在垃圾回收时不会被回收,但是在内存溢出的时候,就会对软引用的对象进行回收。

软引用的主要作用相当于一个缓存功能,在内存充足的情况下直接通过软引用取值,无须从真实数据来源处取值,提升效率;而在内存不足的时候,自动删除这部分缓存数据,保证不会发生内存溢出。类似于Redis对其内存中的数据进行内存淘汰一样,针对的都是缓存数据,也就说不是必须的那部分数据,丢失了也无所谓,大不了从真实数据源处重新再取一遍,存在的意义仅仅在于提升读取数据的效率。

弱引用:

Object obj = new Object();

WaskReference<Object> wf = new WeakReference<Object>(obj);

Obj = null;

这段代码中,wf对 new Object()创建的对象便是一个弱引用,wf.get()便可以获得obj,但有时获取到的是null。只有弱引用的对象在下一次垃圾回收时,便会进行回收。

弱引用存在的作用在于:可以通过弱引用找到一个对象,但是如果这个对象只有弱引用时,那么这个对象就会被回收掉。那么我们可以利用这个特点,在一些场景中进行使用,比如有这样一个需求:当obj强引用存在的时候,可以通过wf.get()获得obj,但是当obj对象的强引用被回收之后,wf也没必要一直引用着这个对象了,可以回收掉这个对象。通过弱引用可以实现多了一个获取对象的渠道,但是该获取渠道又不会阻止对象被回收的效果。

虚引用:

Object obj = new Object();

PhantomReference<Object> pf = new PhantomReference<Object>(obj);

Obj = null;

这段代码中pf对new Object()创建出来的对象便是一个虚引用,pf.get()返回的永远是null,只有虚引用的对象,每次垃圾回收的时候都会被回收。

虚引用存在的意义在于:用于检测对象是否真的已经从内存中删除。

总结:所以垃圾回收器在基于GC Root进行可达性分析来判定什么是垃圾、什么不是垃圾的过程中,同时也会考虑这个可达是强可达,还是软可达、弱可达、虚可达,从而做出合理的垃圾判定。

垃圾已经定位了,那用什么算法来回收垃圾呢?

常见的垃圾回收算法有三种:复制算法、标记-清除算法、标记-整理算法。这三种算法各有优缺点,有各自适用的场景,无优劣之分。

复制算法:

复制算法就是说有把内存划分为两块,A块和B块,第一次使用时比如说使用了B块,就会只在B块中分配对象,当进行垃圾回收的时候,把B块中存活的对象全部复制出来,然后放在A块里面,清空B块;垃圾回收之后,进行第二次使用时,使用A块,依次往复:

复制算法的适用于存活对象比较少的情况,复制的对象越少效率就高,缺点就是内存使用率不高,因为始终有一块内存是闲置的。

标记-清除算法:

分为两个阶段:标记和清除,内存使用了一段时间之后,里面肯定有垃圾对象也有正常对象,第一个阶段进行标记,就是把每个对象标记出是垃圾对象还是正常对象,而具体判断依据就是上面的垃圾判断算法;第二个阶段进行清除,就是把所有垃圾对象进行清除:

标记-清除算法适用于存活对象比较多的情况,清除的对象越少效率就高,缺点是清除之后会产生内存碎片,随着多次垃圾回收,多次标记-清除之后,内存碎片现象会更加严重,导致大对象找不到连续内存进行存放。

标记-整理算法:

分为两个阶段:标记和整理,内存使用了一段时间之后,里面有垃圾对象和正常对象,第一个阶段进行标记,和标记-清除算法中的标记过程一样,把每个对象标记出来是垃圾对象还是正常对象,具体判断依据就是上面的垃圾判断算法;第二个阶段进行整理,就是把垃圾对象清理之后,并且把存活对象进行内存整理,整齐地在内存中排列:

标记-整理算法同样适用于存活对象比较多的情况,清除的对象越少效率就越高,而且通过内存整理解决了内存碎片的问题,但同时也意味着更慢了,因为需要时间来进行内存整理。

什么时候进行垃圾回收?

这里我们先想当然的理解一下,当然是空间满了或是快满了,当新创建的对象放不进来的时候,进行垃圾回收了。具体的后面我们再看详细机制。

1、JVM是如何工作的?_jerry_dyy的博客-CSDN博客_jvm是如何运行的

2、JVM的类加载机制_jerry_dyy的博客-CSDN博客

3、JVM内存区域划分_jerry_dyy的博客-CSDN博客_jvm的内存区域划分

4、JVM垃圾回收机制_jerry_dyy的博客-CSDN博客

5、JVM分代模型--新生代 的垃圾回收_jerry_dyy的博客-CSDN博客_jvm新生代划分

6、JVM分代模型--老年代 的垃圾回收_jerry_dyy的博客-CSDN博客

7、常见的垃圾回收器_jerry_dyy的博客-CSDN博客

8、JVM优化简介_jerry_dyy的博客-CSDN博客

9、学会查看GC日志_jerry_dyy的博客-CSDN博客

10、摸清JVM运行状况_jerry_dyy的博客-CSDN博客

11、摸清JVM对象分布_jerry_dyy的博客-CSDN博客

12、OOM简介_jerry_dyy的博客-CSDN博客

13、OOM模拟_jerry_dyy的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值