JVM垃圾回收

二、垃圾回收算法

  • 哪些是垃圾?标记算法
  • 怎么清除?清除算法

三、垃圾标记阶段

  • 堆里存放着几乎所有的Java对象实例,在GC执行之前,首先需要区分出内存中哪些是存活对象,哪些是已死亡对象
  • 只有被标记死亡的对象,GC才会在执行时,释放掉其所占用的内存空间
  • 当一个对象已经不再被任何的存活对象继续引用时,可以判断为已经死亡
  • 包含引用计数算法和可达性分析算法

1. 引用计数算法

  • Reference Counting
1. 每个对象保存一个人整型的引用计数器属性,用于记录对象被引用的情况
   1.1  对于一个对象A,只要有任何一个对象引用了A, 则A的引用计数器加1
   1.2  当引用失效时,引用计数器减1
   1.3  对象A的引用计数器的值为0,则表示对象A不可能再被使用,就可以回收

2. 优点: 实现简单,垃圾对象方便辨识,判定效率高,回收没有延迟

3. 缺点:
    3.1 需要单独的字段存储计数器,增加   存储空间的开销
    3.2 每次赋值需要更新计数器,伴随着加法和减法,增加了  时间开销
    3.3 致命缺陷: 无法处理循环引用的问题,  最终java的垃圾回收器中没有使用该算法

在这里插入图片描述

package com.nike.erick.d07;

public class Demo02 {
    public static void main(String[] args) {
        ErickService service01 = new ErickService();
        ErickService service02 = new ErickService();

        service01.reference = service02;
        service02.reference = service01;

        service01 = null;
        service02 = null;

         System.gc();
    }
}

class ErickService {
    // 占位符,只是代表对象中的大的对象
    private int[] arr = new int[5 * 1024 * 1024];

    public Object reference;
}

在这里插入图片描述

2. 可达性算法

  • 根搜索算法,追踪性垃圾收集器
  • 简单,执行高效,能有效解决引用计数算法中的循环引用问题,防止内存泄漏的发生
  • Java中选择的就是可达性算法
GC Roots: 一组必须活跃的引用

- 以GC Roots为起始点,从上到下的方式, 搜索被根对象集合所链接的目标对象是否可达
- 内存中国的存活对象都会被根对象集合直接或间接的链接, 搜索走过的路径 《引用链》
- 目标对象没有任何引用链相连,则是不可达的,对象已死
- 只有被根对象集合直接或间接链接的对象才是存活对象

在这里插入图片描述

2.1 哪些元素可以作为根元素

  • 如果一个指针,指向了堆内存的对象,但是该指针又不存在堆中,那么该指针就是一个根 元素
- 虚拟机栈中:各个线程被调用的方法中使用的参数,局部变量中的引用等
- 本地方法栈: 引用的对象
- 方法区中: 类静态属性引用的对象
- 方法区中:常用引用的对象,比如Strign常量池
- 所有被同步锁synchronized持有的对象

同时,如果考虑比如新生代的垃圾回收,那么其他堆空间的对象,也必须作为GC Root的一部分,从而判断
新生代的对象是否是垃圾
  • 如果要使用可达性分析算法来判断内存是否可回收,分析工作必须在一个能保障一致性的快照中进行
  • 因此GC必须 Stop The World 的一个重要原因,枚举根节点时必须要停顿

3. 分析工具- 分析GC Roots

  • 内存泄漏:因为对象不可用,但是又没有释放

3.1 JProfile- IDEA插件

  • 监控内存泄漏

四、清除阶段

1. 标记-清除算法

  • Mark-Sweep算法
当堆中的有效空间被耗尽的时候,就会STW,然后进行两项工作,标记和清除
- 标记: Collector从引用根节点开始遍历,标记所有  《被引用的对象》,一般是在对象的Header
        中记录为可达对象
- 清除: Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在Header中没有标记为可达对象
        则将其回收

在这里插入图片描述

优点:
 1 . 实现比较简单

缺点:
1. 效率不算高
2. 进行GC的时候,需要STW,导致用户体验差
3. 这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表

# 何为清除?
并不是真的空, 而是把需要清除的对象地址保存在空闲的地址列表中
下次有新对象需要加载的时候,判断垃圾的位置空间是否足够
如果足够,就存放。  如果不够, 则 oom

2. 复制算法

  • Coping
将活着的内存空间分为两块, 每次只使用其中一块
1. 垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存快中
2. 清除正在使用的内存快中的所有对象
3. 交换两个内存的角色,最后完成垃圾回收

在这里插入图片描述

优点:
a. 没有标记和清除过程,实现简单,运行高效
b. 复制过去以后保证空间的连续性,不会出现 内存碎片 的问题

缺点:
a. 需要两倍的内存空间


# 
如果系统中的存活对象很多,复制算法需要复制的存活对象数量并不会很大,或者说非常低才行
- 比较适合于朝生夕死的区域

3. 标记-压缩算法

  • Mark-Compact

在这里插入图片描述

1. 效率低,但是不会存在内存碎片问题

五、收集思想

1. 分代收集

  • 每种算法都有好有坏,如何选择一个最合适的算法?
  • 不同的对象的生命周期是不一样的
  • 不同生命周期的对象可以采取不同的收集方式,以便提高回收效率
  • 一般是把Java堆分为新生代和老年代,从而根据不同年代的特点,使用不同的回收算法,以便提高垃圾回收的效率
目前几乎所有的GC都是采用分代收集(Generational Collecting)算法执行垃圾回收的

# 年轻代
- 区域相对老年代小,对象生命周期短,存活率低,回收频繁
- 使用复制算法, 同时使用hotspot的两个survivor的设计来缓解内存问题

# 老年代
- 区域较大,对象生命周期长,存活率高,回收不会很频繁
- 可以使用标记-清除算法    或   标记-压缩算法

2. 增量收集算法

  • 垃圾回收过程中,程序出于STW状体,如果垃圾回收时间长,则会导致应用程序的长时间卡顿
- 可以让垃圾收集线程和应用线程交替执行
- 每次垃圾收集线程只收集一小片区域的内存空间,切换到应用程序线程
- 反复执行,直到垃圾收集完成

总体来说, 增量收集算法的基础仍然是 标记-清除和复制算法
但是允许垃圾收集线程以分阶段的方式完成标记,清理或复制工作

缺点:
- 垃圾回收,间断性执行了应用程序,所以减少了系统的停顿时间
- 但是线程切换和上下文转换的消耗,使得垃圾回收的总体成本上升,造成系统吞吐量的下降

3. 分区算法

  • Region
- 相同条件下,堆空间越大,一次GC需要的时间就越长,产生的STW也就越大
- 将一块大的内存区域分割为多个小块,根据目标的停顿时间,每次合理的回收若干个小区间
  而不是整个堆空间,从而减少一次GC所产生的停顿
- 分代算法按照对象的生命周期划分为两部分
- 分区算法将整个堆空间划分为多个连续的不同的小区间 region
- 每个小区间都独立使用,独立回收

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值