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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值