垃圾回收算法有哪些?

2020-08-25 16:28发布

2条回答
刘洋
1楼 · 2020-08-25 21:26.采纳回答

垃圾检测通常通过建立一个根对象的集合以及建立一个从这些根对象开始能够触及的对象集合来实现。如果正在执行的程序可以访问到根对象和某个对象之间存在引用路径,这个对象就是可触及的。对于程序来说,根对象总是可以访问的。从这些根对象开始,任何可以被触及的对象都被认为是“活动”的对象。无法被触及的对象被认为是垃圾。

虚拟机的根对象集合根据实现不同而不同,包含局部变量中的对象引用和栈帧的操作数栈(以及类变量中的对象引用)、被加载的类的常量池中的对象引用(比如字符串)、传递到本地方法中的没有被本地方法释放的对象引用。任何被根对象引用的对象都是可触及的,从而是活动的,任何被活动的对象引用的对象都是可触及的,程序可以访问任何可触及的对象,所以这些对象应用留在堆中,而对于那些不可触及的对象,程序没有办法访问它们,应该被收集和释放。

区分活动对象和垃圾的两个基本方法是引用计数和跟踪。引用计数垃圾收集器通过为堆中的每个对象保存一个计数来区分活动对象和垃圾对象。这个计数记录下这个对象的引用次数。跟踪垃圾收集器实际上追踪从根节点开始的引用图。在追踪中遇上的对象以某种方式打上标记,当追踪结束时,没有被打上标记的对象就被判定是不可触及的,可以被当作垃圾收集。

==引用计数算法==

给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。

  • 优点:实现简单,判断效率高

  • 缺点:很难解决对象之间的相互循环引用(objA.instance = objB; objB.instance = objA)的问题,所以java语言并没有选用引用计数法管理内存

==根搜索算法==

Java和C#都是使用根搜索算法来判断对象是否存活。通过一系列的名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。

在Java中这些对象可以成为GC Root:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象

  • 方法区中的类静态属性引用的对象

  • 方法区中的常量引用对象

  • 本地方法栈中JNI(即Native方法)的引用对象

==标记-清除算法==

标记-清除算法是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段

  • 标记阶段:标记出可以回收的对象。

  • 清除阶段:回收被标记的对象所占用的空间。

63707281.jpeg

标记-清除算法主要有两个缺点,一个是标记和清除的效率不高,另一个从图中就可以看出,就是容易产生大量不连续的内存碎片,碎片太多可能会导致后续没有足够的连续内存分配给较大的对象,从而提前触发新的一次垃圾收集动作。

==复制算法==

为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划分为两个相等的区域,每次只使用其中一个区域。在垃圾收集时,遍历当前使用的区域,把存活对象复制到另一个区域中,最后将当前使用的区域的可回收的对象进行回收。

90984624.jpeg

这种算法每次都对整个半区进行内存回收,不需要考虑内存碎片的问题,代价就是使用内存为原来的一半。复制算法的效率与存活对象的数目多少有很大的关系,如果存活对象很少,复制算法的效率就会很高。由于绝大多数对象的生命周期很短,并且这些生命周期很短的对象都存于新生代中,所以复制算法被广泛应用于新生代中。

==标记-压缩算法==

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法,因为老年代对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-压缩算法,与标记-清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。

94057049.jpeg

标记-压缩算法解决了标记-清除算法效率低和容易产生大量内存碎片的问题,它被广泛应用于老年代中。

==分代收集算法==

分代收集算法会结合不同的收集算法来处理不同的空间,因此在学习分代收集算法之前我们首先要了解Java堆区的空间划分。Java堆区的空间划分在Java虚拟机中,各种对象的生命周期会有着较大的差别,大部分对象生命周期很短暂,少部分对象生命周期很长,有的甚至与应用程序以及Java虚拟机的运行周期一样长。因此,应该对不同生命周期的对象采取不同的收集策略,根据生命周期长短将它们放到不同的区域,并在不同的区域采用不同的收集算法,这就是分代的概念。现在主流的Java虚拟机的垃圾收集器都采用分代收集算法。Java堆区基于分代的概念,分为新生代和老年代,其中新生代再细分为Eden空间、From Survivor空间和To Survivor空间。因为Eden空间中的大多数生命周期很短,所以新生代的空间划分并不是均分的,HotSpot虚拟机默认Eden空间和两个Survivor空间的所占的比例为8:1。

根据Java堆区的空间划分,垃圾收集的类型分为两种,它们分别如下:

  • Minor Collection:新生代垃圾收集。

  • Full Collection:对老年代进行收集,又可以称作Major Collection,Full Collection通常情况下会伴随至少一次的Minor Collection,它的收集频率较低,耗时较长。

当执行一次Minor Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次Minor Collection 并在From Survivor空间存活的仍年轻的对象也会复制到To Survivor空间。有两种情况Eden空间和From Survivor空间存活的对象不会复制到To Survivor空间, 而是晋升到老年代。一种是存活的对象的分代年龄超过-XX:MaxTenuringThreshold(用于控制对象经历多少次Minor GC 才晋升到老年代)所指定的阈值。另一种是To Survivor空间容量达到阈值。当所有存活的对象被复制到To Survivor空间,或者晋升到老年代,也就意味着Eden空间和From Survivor空间剩下的都是可回收对象。

这个时候GC执行Minor Collection,Eden空间和From Survivor空间都会被清空,新生代存活的对象都存放在To Survivor空间。接下来将From Survivor空间和To Survivor空间互换位置,也就是此前的From Survivor空间成为了现在的To Survivor空间,每次Survivor空间互换都要保证To Survivor空间是空的,这就是复制算法在新生代中的应用。在老年代则会采用标记-压缩算法或标记-清除算法。


隔壁街道小胖子
2楼 · 2020-08-25 16:42

一、 标记-清除算法 Mark-Sweep GC

如字面意思 mark-sweep 分为两个阶段:

  1. 标记阶段:从根集合出发,将所有活动对象及其子对象打上标记

  2. 清除阶段:遍历堆,将非活动对象(未打上标记)的连接到空闲链表上

二、标记-压缩 Mark-Compact

和“标记-清除”相似,不过在标记阶段后它将所有活动对象紧密的排在堆的一侧(压缩),消除了内存碎片, 不过压缩是需要花费计算成本的。如下图过程,标记后需要定位各个活动对象的新内存地址,然后再移动对象,总共搜索了3次堆。

三、引用计数 Reference Counting

引用计数,就是记录每个对象被引用的次数,每次新建对象、赋值引用和删除引用的同时更新计数器,如果计数器值为0则直接回收内存。 很明显,引用计数最大的优势是暂停时间短


相关问题推荐

  • 回答 2

    Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpda...

  • 回答 22

    忙的时候项目期肯定要加班 但是每天加班应该还不至于

  • 回答 108
    已采纳

    虽然Java人才越来越多,但是人才缺口也是很大的,我国对JAVA工程师的需求是所有软件工程师当中需求大的,达到全部需求量的60%-70%,所以Java市场在短时间内不可能饱和。其次,Java市场不断变化,人才需求也会不断增加。马云说过,未来的制造业要的不是石油,...

  • 回答 5
    已采纳

    工信部证书含金量较高。工信部是国务院的下属结构,具有发放资质、证书的资格。其所发放的证书具有较强的权威性,在全国范围内收到认可,含金量通常都比较高。 工信部证书,其含义也就是工信部颁发并承认的某项技能证书,是具有法律效力的,并且是国家认可的...

  • 回答 70
    已采纳

    学Java好不好找工作?看学完Java后能做些什么吧。一、大数据技术Hadoop以及其他大数据处理技术都是用Java或者其他,例如Apache的基于Java 的 HBase和Accumulo以及ElasticSearchas。但是Java在此领域并未占太大空间,但只要Hadoop和ElasticSearchas能够成长壮...

  • 回答 16
    已采纳

    就是java的基础知识啊,比如Java 集合框架;Java 多线程;线程的五种状态;Java 虚拟机;MySQL (InnoDB);Spring 相关;计算机网络;MQ 消息队列诸如此类

  • 回答 12

    #{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础,总体上他们的作用是一致的(为了动态传参),但是在编译过程、是否自动加单引号、安全性、使用场景等方面有很多不同,下面详细比较两者间的区别:1.#{} 是 占位符 :动态解析 ...

  • 回答 62

    没问题的,专科学历也能学习Java开发的,主要看自己感不感兴趣,只要认真学,市面上的培训机构不少都是零基础课程,能跟得上,或是自己先找些资料学习一下。

  • 回答 4

    1、反射对单例模式的破坏采用反射的方式另辟蹊径实例了该类,导致程序中会存在不止一个实例。解决方案其思想就是采用一个全局变量,来标记是否已经实例化过了,如果已经实例化过了,第 二次实例化的时候,抛出异常2、clone()对单例模式的破坏当需要实现单例的...

  • 回答 5

     优点: 一、实例控制  单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 二、灵活性  因为类控制了实例化过程,所以类可以灵活更改实例化过程。 缺点: 一、开销  虽然数量很少,但如果每次对象请求引用时都要...

  • 回答 4

    这个主要是看你数组的长度是多少, 比如之前写过的一个程序有个数组存的是各个客户端的ip地址:string clientIp[4]={XXX, xxx, xxx, xxx};这个时候如果想把hash值对应到上面四个地址的话,就应该对4取余,这个时候p就应该为4...

  • 回答 6

     哈希表的大小 · 关键字的分布情况 · 记录的查找频率 1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。...

  • 回答 6

    哈希表的大小取决于一组质数,原因是在hash函数中,你要用这些质数来做模运算(%)。而分析发现,如果不是用质数来做模运算的话,很多生活中的数据分布,会集中在某些点上。所以这里最后采用了质数做模的除数。 因为用质数做了模的除数,自然存储空间的大小也用质数了...

  • 回答 2

    是啊,哈希函数的设计至关重要,好的哈希函数会尽可能地保证计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间

  • 回答 3

     解码查表优化算法,seo优化

  • 回答 5

    1.对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。2.哈希值就是这个元素的位置。3.如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就...

没有解决我的问题,去提问