Java超详细分析垃圾回收机制

目录
  • 前言
  • 垃圾回收概述
  • 内存溢出和内存泄漏
  • 垃圾回收算法
    • 标记阶段
    • STW(Stop-the-World)
    • 回收阶段
      • 标记-清除算法
      • 复制算法
      • 标记-压缩算法
      • 三种算法的比较
  • 总结

前言

在前面我们对类加载, 运行时数据区 ,执行引擎等作了详细的介绍 , 这节我们来看另一重点 : 垃圾回收.

垃圾回收概述

垃圾回收是java的招牌能力 ,极大的提高了开发效率, java是自动化的垃圾回收, 其他语言有的则需要程序员手动回收 , 那么什么是垃圾呢?

垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃圾。如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。

在早期C/C++时代, 需要手动回收垃圾, 如果一旦疏忽, 还会导致内存泄漏问题

这里引出了两个名词 , 内存溢出和内存泄漏, 先来解释这两个意思

内存溢出和内存泄漏

内存溢出 : 内存被占满, 内存不够用了

内存泄漏 : 程序中存在不被使用的对象(但GC无法判定它们为垃圾), GC(垃圾回收)无法去收集清理它们, 这就导致这块空间一直被占用, 无法释放出来,这就是内存泄漏

一些提供 close() 的对象等, 例如在JDBC中 Connection 没有去关闭等, 这样的越积越多就导致内存泄漏问题 , 内存泄漏越来越多最终会导致内存溢出问题(泄漏逐渐蚕食内存)

实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。

在单例模式下, 单例的生命周期和程序一样长,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生

那么GC主要关心哪块区域的收集呢?

在前面运行时数据区我们说过 , 可以总结如下 : 频繁回收新生区, 较少回收老年区 , 基本不收集方法区(元空间)

垃圾回收算法

在垃圾回收时, 分为两个阶段, 标记阶段和回收阶段 , 这两个阶段使用了不同的算法思想来区分垃圾 , 我们来依次论述

标记阶段

想要清除垃圾, 我们先得了解什么是垃圾 , 那么如何来判断一个对象是否是垃圾呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时, 就已经是垃圾了

标记阶段有两种算法 : 引用计数算法和可达性分析算法

引用计数算法

这个算法思想比较简单 , 就是使用一个计数器, 如果有一个引用指向这个对象, 那么计数器就加1 , 引用失效计数器就减一 . 计数器为0 则表示该对象可回收

但是这个算法有一个严重的问题, 此问题也导致我们现如今已不再使用此算法 : 无法处理循环依赖问题 , 这是一个致命缺陷 , 什么是循环依赖问题呢 ?

如图 , 引用P 指向对象A , 而对象A又指向对象B, 对象B又指向对象C , 对象C继续指向对象A, 此时将引用 P 置null , 此时这三个对象形成了依赖闭环, 但都没有直接的引用去指向它们, 这时如果采用引用计数算法, 这三个都不为0 , 也就无法被回收,出现内存泄漏问题

所以在这种条件下, 我们提出了

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

相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题 ,防止内存泄漏的发生。

基本思路如下 :

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

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

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

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

也就是从根对象往下开始搜索 , 如果目标对象不存在引用链 ,则判断可以回收

public static void main(String[] args) {
   List<Integer> list = new ArrayList();
   while(true){
      list.add(new Random().nextInt());
   }
}

以上程序, list指向的对象作为根对象, 死循环生成的每个随机数都存在引用链, 所以此程序最终会导致内存溢出

public static void main(String[] args) {
   while(true){
      Random r = new Random();
   }
}

上面的虽然存在引用指向, 但每次循环引用都会改变指向, 也就不存在引用链 , 所以每次都会被回收, 不会导致无法回收的问题

那么哪些对象可以作为根对象呢?

1.虚拟机栈中引用的对象 比如:各个线程被调用的方法中使用到的参数、局部变量等。

2.本地方法栈内 JNI(通常说的本地方法)引用的对象

3.方法区中类静态属性引用的对象,比如:Java 类的引用类型静态变量

4.方法区中常量引用的对象,比如:字符串常量池(StringTable)里的引用

5.所有被同步锁 synchronized 持有的对象

6.Java 虚拟机内部的引用。

7.基 本 数 据 类 型 对 应 的 Class 对 象 ,一些常驻 的 异 常 对 象 ( 如 :NullPointerException、OutofMemoryError),系统类加载器。

总结就是 : 栈, 方法区 ,字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析

以上可作为根对象的都有这样的特点 : 活跃, 不可变,存活时间长, 在程序中至关重要. 例如: 静态成员等, 同步锁持有的对象等, 这些都是不能被随意回收的

另外在可达性分析算法枚举根节点(root 对象)时会产生STW(Stop-the-World) , 关于STW , 我们下面来介绍

STW(Stop-the-World)

指的是 GC 事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。

我们再次回到上面的问题 , 执行可达性分析算法为什么需要停顿所有java执行线程呢(STW)?

因为对象的状态是不停变化了 , 如果在我们确定哪个对象是垃圾的时候, 此对象的状态还在不停变化时, 这样是没法分析的 , 此时我们去分析能保持一致性的一个快照(某一时间点的执行状态) ,从而得到一个比较准确的结果

需要注意的是, STW是无法避免的 , 和采用哪款GC也无关, 我们只能去尽量减少停顿的时间,STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。

了解了这些, 接着我们来看回收阶段的算法

回收阶段

当GC识别了垃圾之后, 接着就是垃圾回收了, 这里采用了 3 种不同的算法,接着来介绍

标记-清除算法

顾名思义, 包括两个阶段 : 标记和清除, 不过此处的标记和垃圾标记阶段的标记可是不同的

标记:Collector 从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header 中记录为可达对象。(注意:标记的是被引用的对象,也就是可达对象,并非标记的是即将被清除的垃圾对象)。

清除:Collector 对堆内存从头到尾进行线性的遍历,如果发现某个对象在其 Header 中没有标记为可达对象,则将其回收。

图示如下 :

这里也需要注意, 此清除也不是简单的清除, 发现了垃圾对象后, 会先维护一个空列表用来记录垃圾的地址,下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放(也就是覆盖原有的地址)。

标记 - 清除算法比较基础容易理解, 另外它也有很多缺点 , 例如效率不高, GC时存在STW . 另外可以注意到, 这样做会造成空间不是连续的(空间碎片化) , 此时就需要一个空列表来记录这些地址

复制算法

为了解决标记 - 清除算法在效率方面的缺陷 , 复制算法采用将内存按容量划分的方式, 划分成大小相等的两块 , 每次只使用其中的一块. 算法思想如下 :

将正在使用的存活对象全部复制到另一块未被使用空间 , 摆放整齐 , 然后清空此空间所有对象

复制算法优点是 : 简单高效, 不会出现"碎片"问题

缺点当然也很明显 : 需要两倍的内存空间 , 开销较大 , 另外GC如果采用 G1 垃圾回收器的话 , 它将空间拆成了很多份, 如果采用复制算法, 还需要维护各区之间的关系

对于复制算法的思想而言, 如果对老年区采用此算法, 老年区对象较多,存活周期较长, 这时效率就会有点低 , 所以复制算法大多用于 young 区, 幸存者0 区和幸存者1 区之间的相互转换中

标记-压缩算法

上面我们说过, 复制算法相对于老年区来说, 效率就有点低了 , 所以针对老年区的回收, 就采用了标记 - 压缩算法 , 标记 - 清除算法虽然也可以应用于老年区, 但是效率低下, 容易产生内存碎片

算法思想 :

第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象

第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法 , 标记- 压缩是移动式的 , 将对象在内存中依次排列比维护一个空列表少了不少开销(如果对象排列整齐,当我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可)

优点 : 相对于标记 -清除算法避免了内存碎片化 , 相对于复制算法, 避免开辟额外的空间

缺点 : 从效率上来说是不如复制算法的 , 移动时, 如果存在对象相互引用, 则需要调整引用的位置, 另外移动过程中也会有STW

三种算法的比较

复制算法是效率最高的 , 但是花费空间最大

标记 - 压缩算法虽然较为兼顾 , 但效率也变低, 比标记- 清除多了个整理内存的过程, 比复制算法多了标记的过程

总结

到此关于 jvm 的大部分已经讲述完了, 在后续会再补充两个部分 : 对象的finalize() 方法机制和对象的引用 ,感谢您的阅读与关注 ,谢谢 !!!

到此这篇关于Java超详细分析垃圾回收机制的文章就介绍到这了,更多相关Java垃圾回收内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一文带你回顾Java中的垃圾回收机制

    目录 介绍 重要条款: 使对象符合 GC 条件的方法 请求JVM运行垃圾收集器的方式 定稿 让我们举一个真实的例子,在那里我们使用垃圾收集器的概念. 现在获得正确的输出: 总结 介绍 在 C/C++ 中,程序员负责对象的创建和销毁.通常程序员会忽略无用对象的销毁.由于这种疏忽,在某些时候,为了创建新对象,可能没有足够的内存可用,整个程序将异常终止,导致OutOfMemoryErrors. 但是在 Java 中,程序员不需要关心所有不再使用的对象.垃圾回收机制自动销毁这些对象. 垃圾回收机制是守护

  • Java垃圾回收机制的示例详解

    目录 一.概述 二.对象已死? 1.引用计数算法 2.可达性分析算法 3.四种引用 4.生存还是死亡? 5.回收方法区 三.垃圾收集算法 1.分代收集理论 2.名词解释 3.标记-清除算法 4.标记-复制算法 5.标记-整理算法 一.概述 说起垃圾收集(Garbage Collection,下文简称GC),有不少人把这项技术当作Java语言的伴生产 物.事实上,垃圾收集的历史远远比Java久远,在1960年诞生于麻省理工学院的Lisp是第一门开始使 用内存动态分配和垃圾收集技术的语言.当Lisp

  • java 垃圾回收机制以及经典垃圾回收器详解

    判断对象存活方法 引用计数法:在对象中添加一个引用计数子,每当一个地方引用他时,计数器就加一,当引用失效时,计数器就减一. 会有对象循环引用问题: objA.instance = objB objB.instance = objA objA 有objB 的引用 objB 有 objA 的引用,他们相互引用着对方.导致他们无法回收. 可达性分析: 从GC Roots 根对象作为起点,根据引用关系向下搜索,如果对象可达,就说明对象存活,如果对象不可达,就说明对象可以被回收. GC Roots的根对象

  • Java 中的垃圾回收机制详解

    目录 介绍 重要条款: 使对象符合 GC 条件的方法 请求JVM运行垃圾收集器的方式 定稿 总结 介绍 在 C/C++ 中,程序员负责对象的创建和销毁.通常程序员会忽略无用对象的销毁.由于这种疏忽,在某些时候,为了创建新对象,可能没有足够的内存可用,整个程序将异常终止,导致OutOfMemoryErrors. 但是在 Java 中,程序员不需要关心所有不再使用的对象.垃圾回收机制自动销毁这些对象. 垃圾回收机制是守护线程的最佳示例,因为它始终在后台运行. 垃圾回收机制的主要目标是通过销毁无法访问

  • Java详细分析梳理垃圾回收机制

    目录 Java语言的垃圾回收 1.垃圾回收机制的基本概念 2.Java垃圾回收机制的好处 3.Java垃圾回收机制的特点 总结 Java语言的垃圾回收 1.垃圾回收机制的基本概念 问:1.什么是Java垃圾回收? 答:在Java语言的生命周期中,Java运行环境提供了一个系统的垃圾回收器线程,负责自动回收那些没有引用与之相连的对象所占用的内存.这种清楚无用对象进行内存回收的过程叫做垃圾回收. 问:2.Java垃圾回收的作用是什么? 答:垃圾回收是Java语言提供的一种自动内存回收的功能,可以让程

  • Java jvm垃圾回收详解

    目录 常见面试题 1.JVM内存回收和分配 1.1主要的区域? gc测试 1.2大对象进入老年代 1.3长期存活的对象进入老年代 1.4主要进行gc的区域 gc的类型 Young Gc Full Gc 1.5空间分配担保? 2.对象已经死亡? 2.1引用计数法 2.2可达性分析 2.3再谈引用 虚引用.软引用.弱引用的区别? 2.4不可达对象不一定回收 2.5如何判断一个常量是废弃常量? 2.6如果判断一个类没有用? 3.垃圾回收算法 hotspot为什么要区分老年代和新生代? 跨代收集假说?

  • Java超详细分析垃圾回收机制

    目录 前言 垃圾回收概述 内存溢出和内存泄漏 垃圾回收算法 标记阶段 STW(Stop-the-World) 回收阶段 标记-清除算法 复制算法 标记-压缩算法 三种算法的比较 总结 前言 在前面我们对类加载, 运行时数据区 ,执行引擎等作了详细的介绍 , 这节我们来看另一重点 : 垃圾回收. 垃圾回收概述 垃圾回收是java的招牌能力 ,极大的提高了开发效率, java是自动化的垃圾回收, 其他语言有的则需要程序员手动回收 , 那么什么是垃圾呢? 垃圾是指在运行程序中没有任何引用指向的对象,这

  • Java超详细分析泛型与通配符

    目录 1.泛型 1.1泛型的用法 1.1.1泛型的概念 1.1.2泛型类 1.1.3类型推导 1.2裸类型 1.3擦除机制 1.3.1关于泛型数组 1.3.2泛型的编译与擦除 1.4泛型的上界 1.4.1泛型的上界 1.4.2特殊的泛型上界 1.4.3泛型方法 1.4.4类型推导 2.通配符 2.1通配符的概念 2.2通配符的上界 2.3通配符的下界 题外话: 泛型与通配符是Java语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多. 1.泛型 1.1泛型的用法

  • Java选择排序和垃圾回收机制详情

    目录 一.垃圾回收机制 二.Arrays类 三.选择排序法 四.总结 一.垃圾回收机制 创建对象就会占据内存,如果程序在执行过程中不能再使用某个对象,这个对象是徒耗内存的垃圾.作为程序员不用关心回收垃圾对象问题,因为java虚拟机会自动回收垃圾对象所占用的内存空间. 当一个对象成为垃圾后会暂时保留在内存,如果垃圾堆满了,Java虚拟机有垃圾回收机制,收集到的垃圾对象所占的内存空间,会给垃圾收集器释放.然而程序会有很多的存储空间.也可以通过调用System.gc()方法让java虚拟机进行垃圾回收

  • Java超详细分析抽象类和接口的使用

    目录 什么是抽象类 抽象类语法 总结抽象类: 接口 怎么定义接口 接口间的继承 几个重要的接口 接口comparable comparator接口-比较器 cloneable接口深入理解深拷贝与浅拷贝 怎么使用cloneable接口 浅拷贝: 深拷贝 什么是抽象类 什么是抽象类呢?抽象类顾名思义就是很抽象,就是当我们没有足够的信息去描述这个类的时候我们就可以先不用描述,这样的类就是抽象类. 用代码举个例子: class Shape { public void draw() { System.ou

  • Java超详细分析继承与重写的特点

    概念:继承是面向对象语法三大特征之一,继承可以降低代码的沉余度,提高编程的效率.通过继承子类可以随意调用父类中的某些属性与方法,一个子类只能继承一个父类,一个父类可以被多个子类继承.它就好比与我们显示生活中孩子继承父亲的财产.重写的好处在于子类可以根据需要,定义特定于自己的行为. 也就是说子类能够根据需要实现父类的方法,就好比金毛与哈士奇他的特征都是来自狗,仓鼠与松鼠他们他们的特征来自老鼠,而他们身上的不同属于基因突变就相当于重写 继承的特点: 1):java中只支持单根继承,即一个类只能有一个

  • Java超详细分析讲解哈希表

    目录 哈希表概念 哈希函数的构造 平均数取中法 折叠法 保留余数法 哈希冲突问题以及解决方法 开放地址法 再哈希函数法 公共溢出区法 链式地址法 哈希表的填充因子 代码实现 哈希函数 添加数据 删除数据 判断哈希表是否为空 遍历哈希表 获得哈希表已存键值对个数 哈希表概念 散列表,又称为哈希表(Hash table),采用散列技术将记录存储在一块连续的存储空间中. 在散列表中,我们通过某个函数f,使得存储位置 = f(关键字),这样我们可以不需要比较关键字就可获得需要的记录的存储位置. 散列技术

  • Java超详细分析@Autowired原理

    目录 @Autowired使用 @Autowired源码分析 1.查找所有@Autowired 2. 注入 2.1 字段注入(AutowiredFieldElement) 2.2 方法注入(AutowiredMethodElement) @Autowired使用 构造函数注入 public Class Outer { private Inner inner; @Autowired public Outer(Inner inner) { this.inner = inner; } } 属性注入 p

  • Java超详细分析讲解final关键字的用法

    目录 基本介绍 final细节01 final细节02 基本介绍 final 可以修饰类.属性.方法和局部变量. 在某些情况下,程序员可能有以下需求,就会使用到final: Base Sub 类 1)当不希望类被继承时,可以用final修饰. 2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字 修饰.[案例演示:访问修饰符 final 返回类型方法名] 3)当不希望类的的某个属性的值被修改,可以用final修饰.[案例演示: public final dou

  • PHP5.3的垃圾回收机制(动态存储分配方案)深入理解

    垃圾回收机制是一种动态存储分配方案.它会自动释放程序不再需要的已分配的内存块. 自动回收内存的过程叫垃圾收集.垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑. 在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征,如Python.PHP.Eiffel.C#.Ruby等都使用了垃圾回收机制. 虽然垃圾回收是现在比较流行的做法,但是它的年纪已经不小了.早在20世纪60年代MIT开发的Lisp系统中就已经有了它的身影, 但是由于当时技术条件不成熟,从而使得垃

随机推荐