文章目录

  • 垃圾回收相关算法
    • 标记阶段
      • 引用计数算法
      • 可达性分析算法(根搜索算法、追踪性垃圾收集)
        • 基本思路:
        • GC Roots 有哪几类?
        • 注意
      • 对象的finalization机制
    • 清除阶段
      • 标记-清除算法(Mark-Sweep)
        • 介绍:
        • 执行过程:
        • 优点:
        • 缺点:
      • 复制算法(Copying)
        • 简介:
        • 核心思想:
        • 优点:
        • 缺点:
      • 标记-压缩算法(Mark-Compact)
        • 背景:
        • 步骤:
        • 优点:
        • 缺点:
      • 小结
      • 分代收集算法
        • 背景:
        • 年轻代:
        • 老年代:
    • 补充:增量收集算法、分区算法
      • 增量收集算法
        • 背景:
        • 基本思想:
        • 优点
        • 缺点
      • 分区算法
        • 基本思想:
        • 优点:

垃圾回收相关算法

标记阶段

  • 堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些才是存活对象,哪些是已经死亡的对象。 只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程称为垃圾标记阶段
  • 那么JVM中究竟是如何标记一个死亡对象呢?即,当一个对象不再被任何存活对象继续引用时,即可以宣判已经死亡
  • 判断对象存活一般有两种方式: 引用技术算法可达性分析算法

引用计数算法

  • 每个对象保存一个整型的 引用计数器属性,用于记录对象被引用的情况

  • 对于一个对象,若任意对象引用了该对象,则该对象的引用计数器就+1,当引用失效时,引用计数器-1,引用计数器的值为0,即代表该对象不可能再被使用,可以进行回收

  • 优点:

    • 实现简单
    • 垃圾对象容易辨识
    • 判定效率高
    • 没有延迟性
  • 缺点:

    • 它需要单独的字段存储计数器,增加了存储空间的开销
    • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销
    • 引用计数器有一个严重的问题,即 无法处理循环引用的情况,这是一条致命缺陷,因此Java的垃圾回收器中 没有使用这类算法

可达性分析算法(根搜索算法、追踪性垃圾收集)

  • 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决在引用计数算法中循环引用的问题,防止内存泄漏的发生
  • Java和 C#选择了该算法,这种类型的垃圾收集通常也叫做 追踪性垃圾收集(Tracing Garbage Collection)

基本思路:

  • 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的方式 搜索被根对象集合所连接的目标对象是否可达
  • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或者间接连接着,搜索所走过的路径称为 引用链(Reference Chain)
  • 如果目标对象没有任何引用链相连,则为不可达的,意味着对象已经死亡,可以标记为垃圾对象
  • 可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象

GC Roots 有哪几类?

  • 虚拟机栈中引用的对象(局部变量表)

    • 各个线程被调用的方法使用到的参数、局部变量等
  • 本地方法栈内JNI(本地方法)引用的对象
  • 方法区中静态属性引用的对象
  • 所有被同步锁synchronized持有的对象
  • Java虚拟机内部的引用
    • 基本数据类型对应的Class对象,一些常驻内存中的异常对象(NullPointerException),系统类加载器
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的毁掉、本地代码缓存等。
  • 除了这些固定的集合之外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其它对象“临时性”地加入,共同构成完整GC Roots集合。比如:分代收集和局部回收(Partial GC)
    • 如果只针对Java堆中某一块区域进行垃圾回收,必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的。这个区域的对象完全有可能被其它区域的对象引用。这时候就需要一并将关联的区域对象也加入GC Roots集合中去考虑,才能保证可达性分析的准确性

由于Root采用栈方式存放变量和指针,因此,如果一个指针保存了堆内存里的对象,但是本身不存放在内存堆里,那么它就是一个Root

注意

  • 如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行,这点不满足的话,分析结果的准确性就无法保证。
  • 这点也是导致GC进行时必须“Stop The World”的一个重要原因
    • 即使号称(几乎)不会发生停顿的CMS收集器中,枚举根结点的时候也是必须要停顿的

对象的finalization机制

Java语言提供了对象终止(finalizaztion)机制允许开发人员提供 对象被销毁之前的自定义处理逻辑

  • 当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法,该方法在Object类中。

  • finalize()方法允许在子类中被重写, 用于在对象被回收时进行资源释放。 通常这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。

  • 永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用

    • 主动调用finalize()时可能会导致对象复活
    • finalize()方法的执行时间是没有保障的,它完全由GC决定,极端情况下,若不发生GC,就没有执行机会。
    • 一个糟糕的finalize()会影响GC性能
  • 从功能上来说,finalize()方法与C++中的析构函数比较相似,但是Java采用的是基于垃圾回收器的自动内存管理机制,所以本质上有所不同

  • 由于finalize()的存在,虚拟机中的对象一般被划分成三种可能的状态。只有在不可触及状态时才能被虚拟机回收

    • 可触及的: 从根结点开始,可以达到这个对象(对象可达)
    • 可复活的 对象的所有引用都被释放,但对象有可能在finalize()中复活
    • 不可触及的 对象的finalize被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为 finalize只会被调用一次。
  • 判定一个对象A是否可回收,至少要经历两次标记

    1. 如果对象A到GC Roots 没有引用链,则进行第一次标记

    2. 进行筛选,判断对象是否有必要执行finalize()方法

      ① 如果对象A没有重写finalize方法,或者该方法已经被虚拟机调用过,则虚拟机视为“没有必要执行”,A被判定为不可触及的

      ② 如果A重写了finalize方法,且未执行过,nameA会被插入F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize方法

      finalize方法是对象逃脱死亡最后的机会,稍后GC就会对F-Queue队列中的对象进行第二次标记。如果A在finalize方法中与引用链上的任何一个对象建立了联系,那么在第二次标记时,对象A会被移除“即将回收”集合;然后对象会再次出现没有引用的情况。这个情况下,finalize不会被再次调用,对象会直接变成不可触及的状态,也就是说,一个对象的finalize方法只会被执行一次

清除阶段

当成功区分出内存中存活对象和死亡对象后,GC就会执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。

目前JVM中比较好藏剑的三种垃圾收集算法是: 标记-清除算法、复制算法、标记-压缩算法

标记-清除算法(Mark-Sweep)

介绍:

标记清除算法是一种非常基础和常见的垃圾收集算法,该算法最早被用于Lisp语言中。

执行过程:

当堆中的有效内存空间(available Memory)被耗尽的时候,就会停止整个程序(stop the world,STW),然后进行两项工作。第一项是标记,第二项是清除。

  • 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
  • 清除:Collector对堆内存从头到尾进行线性遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。

注意:从理论上讲,标记对象可以选择被引用对象,也可以选择标记需要清除的对象。但从虚拟机的实现来说,标记的是可达对象。

清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。有新对象需要加载时,判断垃圾的位置空间是否够用,若够用,则直接存放。

优点:

  • 理论简单,便于理解

缺点:

  • 效率不算高,标记时需要遍历,复杂度为O(n),清除环节也需要全遍历,时间复杂度也是O(n)
  • GC时需要停止整个应用程序,用户体验差
  • 该方式清理出来的空闲内存不是连续的,产生内存碎片,因此需要维护一个空闲列表。

复制算法(Copying)

简介:

为了解决标记-清除算法在垃圾回收执行效率方面的缺陷,M.L.Minsky于1963年提出了复制算法,后也被引用到了Lisp的一个实现版本中。

核心思想:

讲或者的内存空间分为两块,每次只使用一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色。最后完成垃圾回收。

survivor 0、 survivor1区就使用了该算法

优点:

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

缺点:

  • 需要两倍的内存空间
  • 对于G1这种拆分成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,无论是内存占用还是时间开销都不小。
  • 若系统中的存活对象很多,该算法效果并不是很理想。

标记-压缩算法(Mark-Compact)

背景:

复制算法的高效性是简历在存活对象少、垃圾对象多的前提下的。但是如果再老年代,更常见的情况是存活对象很多,若此时使用压缩算法,则复制成本将会很高;并且由于老年代空间较大,若使用复制算法,浪费的空间相对也更大。因此需要使用其他的算法

步骤:

  1. 和标记-清除算法的步骤一样,从根结点开始标记所有被引用的对象
  2. 将所有的存活对象压缩到内存的一端,按照顺序存放
  3. 清理边界外的所有空间。

标记压缩算法的效果等同于 标记清除算法完成后,再进行一次内存碎片整理。因此可以把它称之为标记-清除-压缩(Mark-Sweep-Compact)算法

二者的本质差异在于 标记清除算法是一种 非移动式的回收算法,标记-压缩算法是移动式的回收算法。是否移动回收后的存活对象是一个优缺点并存的风险决策。

优点:

  • 消除了标记-清除算法中,内存区域分散的缺点。JVM只需要有一个内存的起点即可,省去了空闲列表的开销。
  • 消除了复制算法中,可用内存减半的高额代价

缺点:

  • 从效率上来说,标记-整理算法要低于复制算法。
  • 移动对象的同时,乳沟对象被其他对象引用,则还需要调整引用的地址。
  • 移动过程中,需要STW,即全程暂停用户应用程序。

小结

Mark-swap(标记清除) Mark-Compact(标记-压缩) Coyping(复制)
速度 中等 最慢 最快
空间开销 少(造成碎片堆积) 少(不堆积碎片) 需要存活对象的两倍大小(不堆积碎片)
移动对象

分代收集算法

背景:

不同对象的生命周期是不一样的,因此不同生命周期的对象可以采用不同的收集方式,以便提高回收效率。一般是把Java堆分成新生代和老年代。这样就可以根据不同的特点使用不同的垃圾回收算法。

目前几乎所有的GC都是分代收集算法执行垃圾回收的

年轻代:

  • 区域相对老年代较小,对象生命周期短,存活率低,回收频繁。

  • 这种情况下,复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此适用于年轻代的回收,而复制算法内存利用率不高的问题,通过Hotspot中的survivor0 和survivor1区的设计得到缓解。

老年代:

  • 老年代特点是 区域较大,对象生命周期长,存活率高,回收不及年轻代频繁。

  • 这种情况存在大量存活的对象,复制算法明显不合适,一般是由标记-清除算法或者标记-压缩算法的混合实现。

    • Mark阶段的开销与存活对象的数量成正比

    • Sweep阶段的开销与所管理区域的大小成正比

    • Compact的开销与存活对象的数据成正比。

以Hotspot中的CMS回收期为例,CMS是基于标记-清除算法实现的,对于对象的回收效率很高。面对碎片问题,CMS采用基于标记-压缩算法的Serial Old回收期作为补偿措施;当内存回收不佳(碎片导致的 Concurrent Mode Failure),将采用Serial Old执行Full GC以达到对老年代内存的整理


补充:增量收集算法、分区算法

增量收集算法

背景:

为了解决STW时,严重影响用户体验或者系统的稳定性的问题,产生了增量收集算法

基本思想:

垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。其基础依然是标记-清除算法,对线程间冲突妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。

优点

解决了严重影响用户体验和系统稳定性的问题。

缺点

由于在垃圾回收过程中,间断性地执行了应用程序的代码,会导致线程间切换和上下文的消耗,使得垃圾回收中体成本上升,造成系统吞吐量的下降。

分区算法

基本思想:

一般来说,相同条件下,堆空间越大,一次GC所徐亚的时间就越长,有关GC的停顿也越长。为了更好控制GC产生的停顿时间,将一块大的区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,从而减少一次GC所产生的停顿时间。

分区算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同的小区间。

优点:

每个小区间独立使用,独立回收,可以控制一次回收多少个小区间。

目前主流的垃圾回收器的关注重点基本不在于吞吐量,而是低延迟。

实际的GC基本都不是使用某个算法,而是使用复合算法。

Java虚拟机(十四)——垃圾回收算法相关推荐

  1. 【Java 虚拟机原理】垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )

    文章目录 一.Java 虚拟机内存分区 二.垃圾回收机制 三.引用计数器算法 ( 无法解决循环引用问题 ) 一.Java 虚拟机内存分区 Java 虚拟机内存分区 : 所有线程共有的内存区域 : 堆 ...

  2. 【深入理解java虚拟机】 - JVM垃圾回收算法

    文章目录 对象是否存活? 引用计数法 可达性分析法 强.软.弱.虚 finalize() 垃圾收集算法 分代收集理论 标记-清除算法 标记-复制算法 标记-整理算法 其他 垃圾回收算法细节实现 根节点 ...

  3. 【Java 虚拟机原理】垃圾回收算法( Java VisualVM 工具 | 安装 Visual GC 插件 | 使用 Java VisualVM 分析 GC 内存 )

    文章目录 一.Java VisualVM 工具安装 Visual GC 插件 二.使用 Java VisualVM 分析 GC 内存 一.Java VisualVM 工具安装 Visual GC 插件 ...

  4. 【Java 虚拟机原理】垃圾回收算法 ( 标记-清除算法 | 复制算法 | 标记-整理算法 )

    文章目录 总结 一.标记-清除算法 二.复制算法 三.标记-整理算法 总结 常用的垃圾回收算法 : 标记-清除算法 ; 复制算法 ; 标记-整理算法 ; 这些算法没有好坏优劣之分 , 都有各自的 优势 ...

  5. 【Java 虚拟机原理】垃圾回收算法 ( 设置 JVM 命令参数输出 GC 日志 | GC 日志输出示例 | GC 日志分析 )

    文章目录 一.设置 JVM 命令参数输出 GC 日志 二.GC 日志示例 三.GC 日志分析 一.设置 JVM 命令参数输出 GC 日志 在 IntelliJ IDEA 的启动参数中设置 -XX:+P ...

  6. 【Java 虚拟机原理】垃圾回收算法 ( 可达性分析算法 | GC Root 示例 | GC 回收前的两次标记 | finalize 方法示例 )

    文章目录 一.可达性分析算法 二.GC Root 示例 三.GC 回收前的两次标记 四.finalize 方法示例 一.可达性分析算法 在 堆内存 中 , 存在一个 根对象 GC Root , GC ...

  7. 《深入理解Java虚拟机》阅读——垃圾回收机制

    <深入理解Java虚拟机>阅读--垃圾回收机制 前言 why--为什么需要垃圾回收 what--垃圾回收做些什么 where--去哪里回收垃圾 how--垃圾回收是怎么做的 垃圾是否要回收 ...

  8. java虚拟机多久触发垃圾回收_每日一问:讲讲 Java 虚拟机的垃圾回收

    昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...

  9. Java基础:JVM垃圾回收算法

    众所周知,Java的垃圾回收是不需要程序员去手动操控的,而是由JVM去完成.本文介绍JVM进行垃圾回收的各种算法. 1. 如何确定某个对象是垃圾 1.1. 引用计数法 1.2. 可达性分析 2. 典型 ...

  10. Java虚拟机专题之垃圾回收(读书笔记)

    一 如何判断对象是垃圾对象 1.1 引用计数法 (Reference Counting) 在对象中添加一个引用计数器,当有其他地方引用这个对象的时候,引用计数器就加1,当引用失效的时候就-1. 当垃圾 ...

最新文章

  1. tomcat7.027-webSocket应用程序构建01
  2. 【转载 译自MarketWatch 】 华尔街疯人日记 (二十五)
  3. apache camel 相关配置_Apache Camel的Java编程入门指南
  4. SpringMvc中ModelAndView模型的应用
  5. 计算机nit题百度云,计算机NIT应用基础试题
  6. POJ 3461Oulipo KMP模板
  7. LeetCode 529. 扫雷游戏(广度优先搜索BFS/深度优先搜索DFS)
  8. redis 字符串数据(string)
  9. ETL异构数据源Datax_部署前置环境_01
  10. unity远程协同共享leap手势
  11. MySQL和数据库可视化工具的下载与安装
  12. winrar注册以及去广告
  13. html颜色怎么渐变效果,html怎么设置颜色渐变
  14. react中axios封装ajax,【逆流而上】[React]axios的封装使用
  15. 编译原理 CS-143(更新至week4)
  16. 问题描述 the jar file jrt-fs.jar has no source attachment
  17. 微信支付API v3接口使用应用篇
  18. 笔记本计算机无线开关在哪里,笔记本的WIFI开关在哪里 笔记本WIFI打开方法
  19. 维护站点和建站时遇到的一些问题
  20. windows10如何开机自动运行bat文件

热门文章

  1. 【BZOJ4199】品酒大会,后缀数组+并查集维护
  2. python rq asyncio_python异步IO-asyncio
  3. 2017.3.1 xiaoyimi测试
  4. 【英语学习】【WOTD】commemorate 释义/词源/示例
  5. 【英语学习】【WOTD】cubit 释义/词源/示例
  6. Intel Core Enhanced Core架构/微架构/流水线 (6) - 指令预译码/指令队列/指令译码
  7. ubuntu系统DD对拷还原报废笔记本
  8. 【Java基础学习笔记】- Day11 - 第四章 引用类型用法总结
  9. IDEA 点击进入方法内部_一份最详细的 IDEA调试教程,让bug无处藏身!
  10. 如何快速开发一个支持高效、高并发的分布式ID生成器(一)