JVM原理:JVM垃圾回收算法(通俗易懂)前言正文总结

+关注继续查看

目录

前言

正文

垃圾标记算法

引用类型

强引用

软引用

弱引用

虚引用

引用计数法

循环引用问题

根可达性分析法

虚拟机栈(栈帧的局部变量表)中的引用

方法区中类静态属性引用

方法区中常量引用

本地方法栈(Native方法)引用

垃圾回收算法

标记清除算法

复制算法

复制算法和标记清除算法如何选择?

标记整理算法

分代收集算法

总结

前言

为什么程序跑久了有时会变卡,如果你看过笔者的 《JVM运行时内存模型》那么应该知道用户线程在跑时会将new出来的对象放到虚拟机堆中,当堆满了之后会发生垃圾回收,而垃圾回收过程中会STW,也就是停止用户线程运行,这种在程序层面看来就是没反应,所以如果出现这种问题,我们就要想办法让STW的时间尽量的变短;

这分为几种情况,垃圾回收时间长或者垃圾回收的频率高;

垃圾回收时间长的情况?

可能是因为GC要扫描的区域比较大有,清理时间较长,或者说我们选择的垃圾回收器不适合当下情况

GC频率过高

可能有部分对象发生内存泄漏,导致无法回收,占用堆空间,此时的堆空间变小,满了就触发GC,频率自然高。也可能是年轻代和老年代空间分配不合理,空间过小导致频繁GC;

正文

认清原理,并接受原理,才能有效地做事情,所以我们要知道有哪些垃圾回收算法、哪些垃圾回收器,了解其特性,才可以帮忙我们进行程序优化。

垃圾标记算法

程序运行时,堆中存储了一堆对象,那垃圾回收器怎么知道哪些需要回收,不会发生误回收的情况呢?当一个实例对象有被引用时可以认为它是个活跃的状态,不进行回收。所以根据这个特性,可以添加一个标记专门记录被引用的次数,当次数不为0的就不进行垃圾回收,这种方式就是引用计数法。还可以在根节点挨个遍历,遍历到的对象进行标记,那些没遍历到的则被当作垃圾处理,这里的根节点可以简单理解为main方法里面开始的都是根,这种方式就是根可达性分析法;目前使用最多的是根可达性分析法;

引用类型

强引用

强引用我们平常用得最多,强引用在内存不足时,垃圾回收器不会进行回收,即使抛出OOM,因为该实例对象被引用,证明不是垃圾数据。

如:

Person person=new Person();

软引用

软引用在内存不足时(证明老年代满了,触发FULL GC),垃圾回收器会进行回收,所以垃圾标记算法在扫描标记时,判断如果引用为软引用,则把它当垃圾处理;

如:

Person person=new Person();

SoftReference sPerson=new SoftReference<>(new Persion());

弱引用

弱引用不管内存够不够时,这里内存够触发GC则证明年轻代满了触发YGC,内存不够则证明是老年代满了触发FULL GC,只要进行垃圾回收就会被当垃圾清除;

如:

Person person=new Person();

WeakReference sPerson=new WeakReference<>(new Persion());

虚引用

虚引用在垃圾回收时被清理,一般配合队列使用,在垃圾回收时会将其加入队列,此时队列可以拿到虚引用,但是虚引用指向的强引用Person已经被回收了,调用phantomReference.get()方法时获取不到实例对象;

 ReferenceQueue QUEUE = new RegferenceQueue<>();
 Person person=new Person();
 PhantomReference phantomReference = new PhantomReference(person, QUEUE);

引用计数法

我们在引用指向对象时就给该对象引用次数加1,在指引指向别的对象或变量销毁时将其-1,在垃圾回收器进行回收时,判断对象的引用标记数是否为0,如果为0则认为它是垃圾数据,对其进行回收;

优点:算法简单,判断搞笑

缺点:无法解决循环引用问题

循环引用问题

public class MyObject {
    public Object ref = null;
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person();
        person1.friend = person2;
        person2.friend = person1;
        person1 = null;
        person2 = null;
    }
}

虽然person1对象和person2对象已经没有任何引用指向它们了,但是此时它们的引用标记数依旧不为0,垃圾回收器认为它们不是垃圾,导致空间无法得到释放;

根可达性分析法

根可达性法以GC ROOTS节点为起点,开始往下扫描,遍历属性引用关系来判断对象是否为可回收的垃圾对象;如果把ROOTS比喻成江河的源头,那么那些未跟源头相通的河或者内湖就可以理解为垃圾对象,三色标记法是比较常用的根可达性分析法;

那么,GC Roots 是什么呢?

虚拟机栈(栈帧的局部变量表)中的引用

public class Demo {
    public static void main(String[] args) {
        // 创建实例A
        // a1:就是虚拟机栈(栈帧的局部变量表)中引用
        A a1 = new A();

        // 当 a1 不再指向实例A的时候,实例A 将不可达
        a1 = null;
    }
}

当开启一个新线程时,会创建线程私有的虚拟机栈,栈帧中维护着局部变量表,线程执行方法中的变量会记录到局部变量表里面,所以可以把局部变量表的变量理解为GC Roots,往下遍历其引用链,在引用链上的都是可用对象;

方法区中类静态属性引用

public class Demo {
    // 创建实例A
    // a2:方法区中类静态属性引用
    public static A a2 = new A();

    public static void main(String[] args) {
        // 当 a2 不再指向实例A的时候,实例A 将不可达
        a2 = null;
    }
}

当属性被static修饰时,其变量会放到方法区中的静态变量区中,静态变量区的变量指向的引用可以看作GC ROOTS;

方法区中常量引用

public class Demo {
    // 创建实例A
    // a3:方法区中常量引用
    public static final A a3 = new A();

    public static void main(String[] args) {
        // 当 a3 不再指向实例A的时候,实例A 将不可达
        a3 = null;
    }
}

当使用final修饰后,其变量会存储到方法区中的常量池中,所以方法区中常量引用可以看作GC ROOTS;

本地方法栈(Native方法)引用

本地方法栈和虚拟机栈类型,也会维护局部变量表,当本地方法区引用了堆中的对象时,可以把其当作GC ROOTS;

垃圾回收算法

标记清除算法

标记清除法分为了标记和清除两个步骤,首先采用可达性分析法进行标记后,对被标记为垃圾的对象进行回收;

黑色为已经扫描过的,有被引用的对象,灰色代表待扫描对象,白色代表待垃圾回收对象;当垃圾回收完成后,此时的空间就变得很碎片化,如果此时需要存入一个对象,该对象占用3个格子的空间,那么第一行的两格空间就没法被利用到;所以碎片化太严重的情况下,空间利用率低,GC的频率就会变高;

复制算法

复制算法会开辟两块内存空间,当标记完成之后,将存活对象拷贝到另外一个空间中,并将当前空间数据清除。这种方式可以解决内存碎片化问题,而且效率高。但意味着我需要更多的内存空间,比如1G的空间,由于需要复制就得拆分成两块500M的空间,所以也是很耗费内容的;

复制算法和标记清除算法如何选择?

一般如果程序对象一般占用空间较小的话,可以采用标记清除算法,这样空间利用率会比复制算法高;如果对象是比较大的话,选择复制算法的空间利用率会比较高,算法没有好坏,只有适不适合;

标记整理算法

标记整理算法在进行标记之后,将存活对象移动到一块,剩下的全部清理掉。这样就有碎片化带来的问题,也不像复制算法那样浪费空间;

分代收集算法

由于在垃圾回收时,会STW暂停用户线程,导致程序没响应,如果停顿时间过长,会导致服务宕机;所以堆空间过大会导致垃圾回收时间过长,所以将堆进行分代管理,分为年轻代和老年代;

当new对象时,会尝试将数据存储到Eden区,如果Eden区满了之后会触发YGC,年轻代采用复制算法,第一次将存活对象复制到survivor0区,第二次会将survivor0和Eden区存活的对象复制到survivor1区,就这样反复在survivor0和survivor1间复制。

当年轻代的存活对象存活时间较长时,导致年轻代剩余空间变小,YGC就会变得很频繁;针对这种情况,给每个存活对象设置存活年龄,每次YGC后未被回收将年龄+1,当年龄到达阈值时(默认15),将其迁移到年轻代中;

当老年代空间满了之后会触发FULL GC,此时会对年轻代和老年代进行垃圾回收,老年代的回收算法不同的垃圾回收器采用的机制不同;

总结

垃圾回收算法有多种,每种针对的场景不一样,没法说其好坏,只有综合业务场景,设备信息等因素挑选出适合程序的算法,现市面上垃圾回收器有多种就是针对不同场景而定制的。

文章来源于互联网:JVM原理:JVM垃圾回收算法(通俗易懂)前言正文总结

下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/17074,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?