• JVM学习----垃圾回收


    1、如何判断对象可以回收

    1.1 引用计数法

    只要一个对象,被其他变量所引用,引用计数就加一。简单说,这个对象被引用几次,引用计数就是几,没有引用就是0。
    弊端:
    循环引用问题:
    这两个对象,都已经没有了其他引用。但是两个相互引用。那么这两个对象都不会被回收(各自引用计数都是1),导致内存泄露现象出现
    在这里插入图片描述

    1.2 可达性分析算法(Java使用)

    首先确定一系列根对象(肯定不能当成垃圾回收的对象----根对象)
    在垃圾回收之前,对堆内存中所有对象进行扫描。看对象是不是被 根对象 直接或者间接的引用。如果是,那么就不回收,如果不是,那么就回收。

    在当前活动线程执行过程中,局部变量所引用的对象是根对象。当局部变量不再引用,那么此时不再是根对象,可以被回收

    1.3 四种引用

    市面上面试常问四种,视频老师认为是五种。(常用类型)
    在这里插入图片描述
    实线:代表强引用

    1. 强引用 : 例如我们new一个对象,通过赋值运算符 赋值给局部变量。这个变量就强引用了这个对象。
      特点:只要沿着 **GC Root对象(根对象)**引用链找到它,就不能被回收。
      我们平常编码使用的都是强引用,你用一个变量去接收new的对象,这就是强引用。
    2. 软引用
      A2对象被C对象间接引用,A2对象被B对象强引用。这种情况不会被垃圾回收。
      如果,A2对象与B对象的强引用断开,只剩软引用。在垃圾回收之后,内存还不够,软引用所引用的对象就会被回收,认为重要性较 低。 当软引用 引用的对象,A2对象被回收时,软引用自身也是对象,它如果在创建时分配了一个引用队列,那么软引用对象就会进入这个
      引用队列。

    设置内存大小为20m的情况下: SoftReference 对象就是软引用对象
    在这里插入图片描述
    这儿我们可以看出,软引用对象并没有被移除。只是软引用 所引用的byte数组对象被回收了。
    在这里插入图片描述

    使用引用队列的软引用----------------------------
    在这里插入图片描述
    结果显示,软引用对象也被回收了。
    在这里插入图片描述

    1. 弱引用
      当A3对象不被B对象强引用,在垃圾回收时,不管内存是否充足,弱引用 所引用的对象都会被回收。(只有Full GC的时候才会触发这种情况,将所有弱引用 所引用的回收。如果是普通的GC则只会将部分 弱引用所引用的对象回收)
      如果弱引用配合了引用队列,那么弱引用也会进入引用队列。

    为什么会使用引用队列:软引用,弱引用,自身也要占用内存,如果要对它们占用的内存进行释放,那么就要借助引用队列。因为它俩还有可能被其他强引用 引用着,只能通过引用队列进行释放内存。
    在这里插入图片描述
    将循环改为10次后测试:
    在这里插入图片描述

    1. 虚引用(直接内存地址)
      必须配合引用队列使用。前面软,弱引用可以配合引用队列也可以不配合引用队列。
      简单说:在我们虚引用 引用的对象被回收时,虚引用对象就会自动进入引用队列,另一个线程调用虚引用的方法,间接回收直接内存。(虚引用典型用法)

    2. 终结器引用
      所有对象都会继承Object对象,Object里面有一个 finalize() 方法。
      当A4对象不被强引用。并且重写了 finalize 方法,那么就A4对象就可以被回收。
      回收步骤:先把终结期引用对象放入引用队列中,然后等一个优先级很低很低的线程 finalizeHandler ,找到A4对象调用finalize方法。调用之后再去回收,终结期引用对象和A4对象。不推荐这种方法引用。
      finalize 方法功效效率很低,要很久可能才会被调用。

    2、垃圾回收算法

    2.1 标记清除

    标记阶段:扫描整个堆内存,只要没有GC Root 直接间接的引用,就可以当做垃圾进行回收
    清除阶段:释放资源(这个就涉及计算机底层内存空间管理了)
    优点:速度快
    缺点:容易产生内存碎片
    在这里插入图片描述

    2.2 标记整理

    标记阶段:和标记清除一样的
    整理阶段:它会将多个空闲碎片整合成一个大的空闲空间。
    优点:没有内存碎片
    缺点:整理过程中,涉及对象移动,效率较低。
    在这里插入图片描述

    2.3 复制

    将内存划分为两个区域。
    将FROM区域不回收的对象标记出来,复制到另一个空闲的内存区域TO,进行连续存放。并且把FROM区域全部清空。
    FROM清空之后,再更改区域定义,TO永远为空闲的那个内存区域。

    优点:不会产生内存碎片
    缺点:会占用双倍的内存空间
    在这里插入图片描述
    在这里插入图片描述

    这三种情况,根据实际情况综合使用

    3、分代垃圾回收

    整个堆内存 综的 划分为 两块
    在这里插入图片描述
    长时间使用的对象,放在老年代。用完就丢的对象,放在新生代中。
    生命周期长的,和生命周期短的
    针对对象不同的生命周期特点,使用不同的垃圾回收机制。例如,新生代的垃圾回收频繁一些,老年代垃圾回收频率低一些。
    不同区域采用不同算法,这样效率更高。

    分析一下:垃圾回收基本流程
    创建一个新的对象时,默认就会采用 伊甸园 内存中。当 伊甸园 空间快满了。就会触发一次垃圾回收。这个垃圾回收被称为Minor GC(新生代的垃圾回收),动作小的垃圾回收。
    采用复制算法,将存活对象复制到 TO 区域中。让幸存对象的寿命 +1。然后FROM 和 TO 交换定义位置。

    伊甸园空间充足了,继续向里添加对象,当快满的时候,将伊甸园存活的对象和 幸存区From区域的继续存活的对象标记出来。复制到TO区域中。
    那么, 从伊甸园新进入的对象寿命+1,幸存区中继续存活的对象 寿命 +1变为2。然后清空伊甸园区域。FROM 和 TO 交换定义区域。

    幸存区FROM中的寿命,超过预设值(最大15),那么将该对象 晋升到 老年代中。

    当老年代区域都快满了,先触发Minor GC,回收之后空间仍不足,则触发一次 Full GC ,将新生代和老年代整个进行清理。

    要是full GC之后还是不够,则报错 OutOfMemeryError

    垃圾回收过程中,全部用户线程暂停。因为涉及对象位置迁移。暂停时间:Full GC > Minor GC
    老年代采用标记整理算法。

    当你存储的对象过大时,会直接晋升到老年代,并且不会触发垃圾回收
    垃圾回收时:当新生代内存紧张时,也不会遵从阈值,将部分对象晋升到老年代。
    多线程时,当一个线程内存溢出,不会影响java整个进程的结束,只会是这个线程死了(那么这个线程的所有资源进行释放)。

    相关VM参数:

    堆初始大小-----------------> -Xms // -Xms20M
    堆最大大小-----------------> -Xmx 或 -XX:MaxHeapSize=size
    新生代大小-----------------> -Xmn 初始和最大同时指定 或 ( -XX:NewSize=size / -XX:MaxNewSize=size 初始大小 / 最大大小)
    幸存区比例-----------------> 动态, -XX:InitialSurvivorRatio=ratio 初始比例 和 -XX:+UserAdaptiveSizePolicy 打开动态
    幸存区比例-----------------> -XX:SurvivorRation=ratio // 默认比例为8,比如10M,Eden为8,from 和 to 各1
    晋升阈值 -----------------> -XX:MaxTenuringThreshold=大小
    晋升详情 -----------------> -XX:+PrintTenuringDistribution // 打印晋升详情
    GC详情 -----------------> -XX:+PrintGCDetails -verbose:gc
    Full GC 前 Minor GC -----------------> -XX:+ScavengeBeforeFullGC // Full GC之前,先做Minor GC,默认打开的

    4、垃圾回收器

    4.1串行

    底层是一个单线程的垃圾回收器,在垃圾回收时其他线程都暂停。
    使用场景:堆内存较小,适合个人电脑(CPU核数少的)。

    开启串行垃圾回收: -XX:+UseSerialGC=Serial+SerialOld
    Serial :工作在新生代 (复制算法) SerialOld:工作在老年代(标记整理)
    在这里插入图片描述

    4.2 吞吐量优先

    多线程,适合堆内存较大,需要多核CPU支持
    单位时间内,STW时间最短 例如:一小时内发生两次垃圾回收,每次0.2s。多量少次

    开启垃圾回收: -XX:+UseParallelGC 新生代(复制) -XX:+UseParallelOldGC 老年代(标记整理)
    1.8 默认使用这种回收器,只需要开启一个,另一个自动开启。
    -XX:ParallelGCThreads = n // 设置参与垃圾回收的线程数
    -XX:+UseAdaptiveSizePolicy // 开启之后,ParallelGC会动态的去调整,新生代的eden区域和survivor区域的比例,包括整个堆的大小,晋升阈值也会受影响
    -XX:GCTimeRatio=n // 调整吞吐量的,默认99
    有一个计算公式, 1 / (1+ratio) ,如果采用默认,那么等式 = 0.01,意思说:你垃圾回收的时间不能超过总时间的 0.01,比如100分钟之内,只能用1分钟用于垃圾回收,如果达不到这个目标, Parallel GC就会调整堆内存大小,使之达到这个目标。一般是把堆增大,垃圾回收就不会这么频繁,那么总时间增大,在120分钟的时候触发了垃圾回收,单次垃圾回收吞吐量增大(1.2分钟用于垃圾回收)。

    -XX:MaxGCPauseMillis=ms // 最大暂停ms数,默认200ms
    单次垃圾回收时间最多 200ms,和上面相悖。

    垃圾回收器会开启多个线程。
    在这里插入图片描述

    响应时间优先

    多线程,适合堆内存较大,需要多核CPU支持
    注重在尽可能让 单次STW时间最短 例如:一小时内发生 5次垃圾回收,每次 0.1s。多次少量

    开启垃圾回收器:
    -XX:+UseConcMarkSweepGC (老年代,标记清除,垃圾回收和用户线程可以并发执行,部分阶段,如果并发失败,则退化为 SeriaOld 退化为单线程的 标记整理)
    -XX:+UseParNewGC (新生代,复制算法)

    工作流程:
    老年代内存不足,发生GC。
    1、初始标记的时候,还是STW,初始标记很快,只会标记根对象(不会挨个扫描标记)。
    2、和用户线程并发执行,根据根对象把其他垃圾线程找出来。
    3、重新标记,需要STW,并发标记同时,用户线程可能产生新的对象,对其他对象改变引用。会对垃圾回收造成干扰。所以需要重新标记工作
    4、并发清理和用户线程并发执行

    细节问题:
    -XX:ParallelGCThread=n //并行垃圾回收线程数设置,一般和cpu核数一致
    -XX:ConcGCThreads=n // 初始标记,并发标记 线程数设置,一般设置为上面参数的 1/4

    在执行并发清理的时候,可能产生新的垃圾,那么在清理的时无法把这些新垃圾清理掉,只能等下次垃圾回收。这些垃圾叫互动垃圾
    所以我们需要预留空间保留这些 互动垃圾
    -XX:CMSInitiatingOccupancyFraction=65% // 用于设置 老年代 内存占用达到多少,触发垃圾回收,剩余空间就使用来存互动垃圾

    -XX:+CMSScavengBeforeRemark // 新生代可能会引用一些老年代的对象,那么我们需要进行一次扫描,而且新生代的对象还有可能是垃圾对象,那么我们扫描就是做无用功。这个参数就是在 重新标记之前对新生代进行一次垃圾回收。减少重新标记压力

    特点:使用的是标记清除,容易产生垃圾碎片,在碎片过多情况下。一个大对象来了,新生代和老年代都放不下。这样就会造成并发失败。这样垃圾回收就会退化 SerialOld(单线程的标记整理),这样垃圾回收时间一下就会增高很多!!!这是存在的最大问题!!!
    在这里插入图片描述

    这个不重要,想看就看吧
    这个算法好复杂,我晕了。。。。
    https://www.bilibili.com/video/BV1yE411Z7AP?p=71&vd_source=f14d0ab6a0fab741200e54ed49c444e6

  • 相关阅读:
    JVM学习笔记
    中国石油大学(北京)-《油藏工程》第一阶段在线作业
    WCF异常System.ServiceModel.ProtocolException问题处理
    02 DevOps 之 Jenkins
    【docker启动的Jenkins时,遇到时区问题处理】
    基于机器学习和奇异值分解SVD的电池剩余使用寿命预测(Python)
    学习C语言的好处:
    Dubbo3应用开发—Dubbo服务管理平台DubboAdmin介绍、安装、测试
    微信小程序----父子组件之间通信
    array.prototype.includes 和 Set.has() 哪一个更好
  • 原文地址:https://blog.csdn.net/m0_48639280/article/details/126031309