JVM系列02-垃圾回收算法

上一章JVM系列01-内存区域中我们主要总结了Java运行时数据区域主要有堆、方法区、虚拟机栈、本地方法栈、程序计数器每个区域具体的存储内容,以及可能发生的异常。这一章我们重点来说一说JAVA相对于C、C++语言来说的一个重要特性垃圾回收。

哪些区域回收?

垃圾回收使得JAVA程序员从此再也不必在担心释放内存空间,将对象的生死大权全都交于JVM动态内存管理机制-垃圾回收来管理。垃圾回收既然这么神奇,那么我们更有必要来了解一下其究竟是怎么回收对象的,根据上一章JVM系列01-内存区域 我们了解到java运行时内存空间主要有虚拟机栈、本地方法栈,这部分空间区域为线程私有,随着线程的生而生,随着线程的结束而销毁,所以这部分空间我们不做重点研究其也不是垃圾回收的关心的区域,而堆、方法区是真正存储对象和类信息的区域,也正是垃圾回收所关心的重点区域。

哪些对象可以回收?

通过上一节,我们了解到堆和方法区是垃圾回收的重点区域。相比于堆上频繁的垃圾回收,我们先来聊一聊方法区的垃圾回收。

  1. 方法区

方法区我们已经了解主要存储着类的原始信息,在JDK1.7之前还存储着字符串常量池,所以这部分区域的垃圾回收主要围绕如何回收类以及字符串常量。

回收字符串常量:假设字符串常量池中存在“Bryant”字符串,当堆上没有任何对象引用它,也就是说再也没有String=“Bryant”的对象时,如果发生垃圾回收并且有必要的话,这个字符串将会比回收,同样的类、接口、方法的符号引用也是如此。但是要判定一个类可以被回收,却是比较麻烦的一件事,需要判断一下条件:

  • 堆上不存在该对象的任何实例,也就是说该类的所有实例都已经被回收了。
  • 该类的加载器classLoader已经被回收
  • 该列的java.lang.class对象没有在任何地方使用,无法通过反射访问该类的方法
    只有满足上述条件的类,虚拟机才有可能卸载该类,也只是可能。

2.堆

堆是对象分配的主要区域,也是垃圾回收的重点区域,哪么如何判断一个对象是否可以被回收呢?主要的思想有以下两种:

  • 引用计数法

    引用计数法的主要思想是这样的:给对象增加一个计数器,每当这个对象被引用的使用,计数器的值就加1,相反的当引用失效的时候,计数器的值就减1,每当计数器的为0的时候,那么就可以判定该对象可能会被回收。计数器思想存在一个问题就是当对象之间存在相互引用,但这两对象都不在使用的时候,无法做到垃圾回收,所以以我们最常用的Hotspot虚拟机为例,其不是用这种思想判定对象是否可以被回收,但这种思想也有他的优点就是非常容易理解,实现简单,所以也有虚拟机确实是使用这种思想去判定对象是否可以回收。

  • 可达性分析算法

上面我们说引用计数法存在对相互引用的对象无法实现判定其可以回收,所以HotSpot虚拟机是基于可达性分析的算法实现垃圾是否可以被回收的判定。那么什么是可达性分性呢?可达性分析算法认为在虚拟机中存在一批称为GC Root的根元素,每一个被真正引用的对象其都会通过链路与GC Root根元素相连接,相反如果对象之间确实存在引用链路连接,但是其到GC Root根节点没有链路可达,那么这些相互连接的对象依然被判定为可以被回收的对象,这很好地解决了相互引用对象无法回收的情况,因此该思想也成为Hotspot虚拟机的垃圾回收思想。

可达性分析
如上图,虽然Object5、object6、object7之间存在引用关系,但是其到GC Root不可达,那么在可达性分析的思想下Object5、object6、object7是可以被回收的对象。那么究竟什么是GC Root的根呢?GC的根是堆上的对象,而GC Root却由堆之外的元素确定,比如:

  • 虚拟机栈上的引用的对象
  • 本地方法栈上引用的对象
  • 方法区中类的静态属性引用的对象
  • 方法区中常量引用的对象

是不是由上述两种思想判定的对象一定会被垃圾回收机制回收呢?答案是不确定的,具体的回收时机还要分析具体的垃圾回收算法以及对应的垃圾回收器,那么常用的垃圾回收算法都有哪些呢?

垃圾回收算法

下面简单介绍介绍几种常见的垃圾回收算法:

  • 标记-清除算法

标记-清除算法顾名思义就是分两个阶段:标记阶段、清除阶段。首先标记出所有需要回收的对象,然后统一清除掉这些对象。如下图所示:

标记-清除算法
如上图所示,经过标记-清除之后会存在大量的内存碎片,此时当需要为一个大对象分配内存空间的时候,无法找到连续的大内存空间,不得不再次触发一次垃圾收集过程,同时效率也不是很高,但该算法确是垃圾回收算法的基础,因为以下的算法都是标记-清除算法的优化或者扩展。

  • 复制算法

如标记-清除算法会产生大量的内存碎片,所以复制算法的思想是:将内存分为两份,一部分用来存储分配的对象,垃圾回收的时候,将依然存活的对象复制到另外一份空间,同时清除这份空间的这个区域,这样一来就不是存在内存碎片的问题,但同时带来的问题是内存的浪费,因为同一时刻只有一部分内存在使用,复制算法如下图:

复制算法
当然垃圾回收器具体实现的时候并不是完全按照复制算法的思想,下面如介绍到的Hotspot垃圾收集器,借助复制算法但同时避免内存的浪费将新生代分为eden、survivor from、survivor to按照8:1:1的比例,投入eden和其中一份的survivor分配对象,留下一份survivor用来保存垃圾回收之后依然存活的对象,提高内存使用率,至于为什么是8:1:1的比例,是因为Hotspot团队经过大量分析实验发现分配在堆上的对象98%都是朝生夕灭的短生命周期对象,既然回收之后有超过10%的对象依然存活,也可以使用老年达作为其担保分配的内存。

  • 标记-整理算法

标记-整理算法也是标记-清除算法的优化,同样的也分为两个阶段标记阶段、整理阶段,与标记-清除所不同的是其会对清除之后的对象作简单的整理,将所有存活的对象移动至一端,然后清理掉另一端的空间从而避免内存碎片的产生

标记-整理算法

  • 分代收集算法

分代算法其实并不是什么新的垃圾回收算法,只是将堆内存空间划分为不同的区域:新生代、老年代,然后具体的年代采用合适的回收算法,比如大量的对象都会分配在新生代同时新生代的对象都是朝生夕灭的,所以新生代划分为8:1:1的eden和survivor区域采用复制算法,而老年达采用标记-整理或者标记-清除根据具体的垃圾回收器实现。

至此,我们已经简单的总结了,什么区域会发生垃圾回收?哪些对象可能会被回收?以及如何回收的算法思想,鉴于篇幅限制,具体的如何回收,我们将在下一章JVM系列03-垃圾回收器中做详细的解释。其中难免存在笔误如何理解不到位的地方,还请各位读者批评指正!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值