关于java的垃圾回收机制,下面哪些结论,java支持的垃圾回收算法

  关于java的垃圾回收机制,下面哪些结论,java支持的垃圾回收算法

  00-1010垃圾收集简介内存溢出和内存泄漏概述垃圾收集算法标记阶段STW(Stop-the-World)回收阶段标记-清除算法复制算法标记-压缩算法三种算法的比较和总结

  00-1010之前我们详细介绍了类加载,运行时数据区,执行引擎等等。在本节中,我们将了解另一个关键点,3360垃圾收集。

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

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

  在早期的C/C时代,垃圾是手动收集的,如果忽略了,就会导致内存泄漏。

  这里有两个术语,内存溢出和内存泄漏。先解释一下这两个意思。

  00-1010内存溢出:内存已满,内存不足。

  内存泄漏3360程序中有未使用的对象(但GC无法判断为垃圾),GC(垃圾收集)无法收集清理,导致这个空间一直被占用,无法释放。这就是内存泄漏。

  一些提供close()的对象,例如,在JDBC连接不关闭等。而这些对象的累积会导致内存泄漏,最终导致内存溢出(泄漏逐渐蚕食内存)。

  其实很多时候,一些不好的做法(或者疏忽)会导致对象的生命周期变长甚至OOM,这也可以称为广义的“内存泄漏”。

  在单例模式下,单例的生命周期与程序的生命周期一样长。如果你持有一个对外部对象的引用,那么这个外部对象就不能被回收,这将导致内存泄漏。

  那么GC主要关注收集哪个领域呢?

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

  00-1010垃圾收集分为两个阶段,标记阶段和回收阶段。这两个阶段使用不同的算法思想来区分垃圾。让我们依次讨论它们。

  00-1010清除垃圾首先要知道什么是垃圾,那么如何判断一个对象是不是垃圾呢?简单地说,当一个对象不再被任何幸存的对象引用时,它就是垃圾。

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

  引用计数算法

  这个算法的思路很简单,就是用一个计数器。如果引用指向此对象,计数器将加1,引用无效计数器将减1。计数器为0意味着该对象可以被回收。

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

  如图,引用P指向对象A,对象B指向对象C,对象C继续指向对象A,此时引用P设置为空。此时,这三个对象形成了一个依赖闭环,但是没有直接的引用指向它们。这时如果采用引用计数算法,这三个对象就不会为零,所以不会被回收,造成内存泄漏。

  所以在这种情况下,我们提出

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

  与引用计数算法相比,可达性分析算法不仅具有实现简单、执行高效的特点,更重要的是能够有效解决引用计数算法中的循环引用问题,防止内存泄漏的发生。

  基本思路如下:3360

  1.可达性分析算法从根对象(GCRoots)开始,从上到下搜索由根对象集连接的目标对象是否可达。

  2.使用可达性分析算法后,内存中幸存的对象会被根对象集直接或间接连接起来,搜索的路径称为引用链。

  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垃圾回收内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: