JVM(三) 垃圾回收

1. 判断对象是否可以回收

引用计数法

引用计数算法(Reference Counting):对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用计数器就加 1;当引用失效时,引用计数器就减 1;当对象 A 的引用计数器的值为 0,即表示对象A不可能再被使用,可进行回收(Java 没有采用)

优点:

  • 回收没有延迟性,无需等到内存不够的时候才开始回收,运行时根据对象计数器是否为 0,可以直接回收
  • 在垃圾回收过程中,应用无需挂起;如果申请内存时,内存不足,则立刻报 OOM 错误
  • 区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象

缺点:

  • 每次对象被引用时,都需要去更新计数器,有一点时间开销

  • 浪费 CPU 资源,即使内存够用,仍然在运行时进行计数器的统计。

  • 无法解决循环引用问题,会引发内存泄露(最大的缺点)

可达性分析算法

工作原理

可达性分析算法以根对象集合(GCRoots)为起始点,从上至下的方式搜索被根对象集合所连接的目标对象

分析工作必须在一个保障一致性的快照中进行,否则结果的准确性无法保证,这也是导致 GC 进行时必须 Stop The World 的一个原因

GC Roots 对象:

  • 虚拟机栈中局部变量表中引用的对象:各个线程被调用的方法中使用到的参数、局部变量等
  • 本地方法栈中引用的对象
  • 堆中类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 字符串常量池(string Table)里的引用
  • 同步锁 synchronized 持有的对象

基本原理

  • 可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索走过的路径称为引用链

  • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象

  • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象

引用分析

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关,Java 提供了四种强度不同的引用类型

  1. 强引用:被强引用关联的对象不会被回收,只有所有 GCRoots 都不通过强引用引用该对象,才能被垃圾回收

    • 强引用可以直接访问目标对象
    • 虚拟机宁愿抛出 OOM 异常,也不会回收强引用所指向对象
    • 强引用可能导致内存泄漏
    Object obj = new Object();//使用 new 一个新对象的方式来创建强引用
  2. 软引用(SoftReference):被软引用关联的对象只有在内存不够的情况下才会被回收

    • 仅有软引用引用该对象时(可能有强引用,一个对象可以被多个引用),在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
    • 配合引用队列来释放软引用自身,在构造软引用时,可以指定一个引用队列,当软引用对象被回收时,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况
    • 软引用通常用来实现内存敏感的缓存,比如高速缓存就有用到软引用;如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时不会耗尽内存
    Object obj = new Object();
    SoftReference<Object> sf = new SoftReference<Object>(obj);
    obj = null;  // 使对象只被软引用关联
  3. 弱引用(WeakReference):被弱引用关联的对象一定会被回收,只能存活到下一次垃圾回收发生之前

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 配合引用队列来释放弱引用自身
    • WeakHashMap 用来存储图片信息,可以在内存不足的时候及时回收,避免了 OOM
    Object obj = new Object();
    WeakReference<Object> wf = new WeakReference<Object>(obj);
    obj = null;
  4. 虚引用(PhantomReference):也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个

    • 一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象
    • 为对象设置虚引用的唯一目的是在于跟踪垃圾回收过程,能在这个对象被回收时收到一个系统通知
    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
    Object obj = new Object();
    PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
    obj = null;
  5. 终结器引用(finalization)

2. 垃圾收集算法

分代收集理论

新创建的对象都被放在了新生代的伊甸园

当伊甸园中的内存不足时,就会进行一次垃圾回收,这时的回收叫做 Minor GC

Minor GC 会将伊甸园和幸存区FROM存活的对象复制到 幸存区 TO中, 并让其寿命加1,再交换两个幸存区

再次创建对象,若新生代的伊甸园又满了,则会再次触发 Minor GC(会触发 stop the world,暂停其他用户线程,只让垃圾回收线程工作),这时不仅会回收伊甸园中的垃圾,还会回收幸存区中的垃圾,再将活跃对象复制到幸存区TO中。回收以后会交换两个幸存区,并让幸存区中的对象寿命加1

如果幸存区中的对象的寿命超过某个阈值(最大为15,4bit),就会被放入老年代

如果新生代老年代中的内存都满了,就会先触发Minor GC,再触发Full GC,扫描新生代和老年代中所有不再使用的对象并回收

GC 分析

大对象处理策略

当遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代

线程内存溢出

某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行

这是因为当一个线程抛出OOM异常后它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常

标记-清除算法

当成功区分出内存中存活对象和死亡对象后,GC 接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在 JVM 中比较常见的三种垃圾收集算法:

  • 标记清除算法(Mark-Sweep)
  • 复制算法(copying)
  • 标记压缩算法(Mark-Compact)

标记清除算法,是将垃圾回收分为两个阶段,分别是标记和清除

  • 标记:Collector 从引用根节点开始遍历,标记所有被引用的对象,一般是在对象的 Header 中记录为可达对象,标记的是引用的对象,不是垃圾

  • 清除:Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在其 Header 中没有标记为可达对象,则将其回收,把分块连接到空闲列表的单向链表,判断回收后的分块与前一个空闲分块是否连续,若连续会合并这两个分块,之后进行分配时只需要遍历这个空闲列表,就可以找到分块

  • 分配阶段:程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block,如果找到的块等于 size,会直接返回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 block - size 的两部分,返回大小为 size 的分块,并把大小为 block - size 的块返回给空闲列表

算法缺点:

  • 标记和清除过程效率都不高
  • 会产生大量不连续的内存碎片,导致无法给大对象分配内存,需要维护一个空闲链表

标记-复制算法

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清理,交换两个内存的角色,完成垃圾的回收

应用场景:如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之则不适合

法算优点: 

  • 没有标记和清除过程,实现简单,运行高效
  • 复制过去以后保证空间的连续性,不会出现“碎片”问题。

算法缺点:

  • 主要不足是只使用了内存的一半
  • 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销都不小

现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间

标记-整理算法

标记整理(压缩)算法是在标记清除算法的基础之上,做了优化改进的算法

标记阶段和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的直接清理可回收对象,而是将存活对象都向内存另一端移动,然后清理边界以外的垃圾,从而解决了碎片化的问题

优点:不会产生内存碎片

缺点:需要移动大量对象,处理效率比较低

Mark-SweepMark-CompactCopying
速度中等最慢最快
空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的 2 倍大小(不堆积碎片)
移动对象
  • 效率上来说,复制算法最好,但是却浪费了太多内存
  • 为了尽量兼顾三个指标,标记一整理算法相对来说更平滑一些

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值