前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM (标记-清除算法、复制算法、标记-整理算法、分代收集算法、分区算法)

JVM (标记-清除算法、复制算法、标记-整理算法、分代收集算法、分区算法)

作者头像
逍遥壮士
发布2021-07-29 16:42:27
2.1K0
发布2021-07-29 16:42:27
举报
文章被收录于专栏:技术趋势技术趋势

JVM如何判断对象是否存活?

在Java堆中存放着所有Java的对象实例 ,在GC执行垃圾收回之前,JVM需要标识出来哪些是对象已经不被引用(垃圾),哪些被引用,而JVM有两种垃圾标识对象是否存活,分别是:引用计数算法可达性分析算法

引用计数算法(Reference Counting)

实现比较简单,对每一个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。

优点:实现比较简单,很容易判断是否是垃圾,判断效率高,回收没有延迟性。

缺点:

需要单独的字段来存储,增加空间的开销;

每次值的更新需要更新计数器,伴随加法和减法操作,增加一定时延;

引用计数器有一个严重的问题,无法处理循环引用的问题,导致不知道是否是垃圾还是存活类似A->B->A的问题,会造成内存泄漏。

可达性分析算法(Reachability Analysis):

可达性分析算法是java采用来判断对象是否存在的算法,通过判断"GC Roots"是否被直接或间接引用,这种会被可达性分析通过搜索路劲找到,而这个路劲叫引用链(Reference Chain),如果被存在则证明是可达的,若不是被引用则证明对象是可以被回收的。依据只是判断该GC Roots上在的这个对象是否存活,实现稍微比较复杂。

可达性分析算法GC Roots的对象包含有哪些?

虚拟机栈引用的对象

本地方法栈内JNI(本地方法)引用的对象

方法区中常量引用的对象(字符串常量池)

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

Java虚拟机内部的引用

如何判断对象是否存活?

对象有三种状态:可触及、可复活、不可触及,分别代表如下:

可触及:从根节点开始,可以达到这个对象;

可复活:对象的所有引用都被释放,但是对象有可能在finalize()中复活;

不可触及:对象finalize()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会被调用一次。

对象被回收前,首先可达性分析到这个对象是否有与GC Roots的引用链,如果没有会被打上第一次标记,然后判断是否有必要执行finalize,如果被执行过或没必要就直接被垃圾回收了。如果有必要,执行后会被入到F-Queue这个队列中,然后逃过一次被GC。等到下一次GC的时候会对F-Queue这个对队列再做一次的标记,如果这次再发现没有引用链就会被直接GC回收了。

Java 中都有哪些引用类型?

强引用:发生 gc 的时候不会被回收。new

软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。SoftReference

弱引用:有用但不是必须的对象,在下一次GC时会被回收。WeakReference

虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

PhantomReference pr = new PhantomReference (object, queue);

可达性分析算法是JAVA采用判断对象存活的算法。

JVM收集器算法有哪些呢?

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

标记---清除算法(Mark-Sweep)是一种非常基础和常见的垃圾收集算法,该算法被J.McCarthy等人在1960年提出并并应用于Lisp语言。标记清除的执行过程是先标记,再清除。

特点:实现简单

缺点:每次清除的时候都需要停机、存在内存空间太强片化问题。

复制算法(Copying)

复制(Copying)算法是为了解决标记-清除算法,的效率和收集的时间空间不连续等问题。主要的实现是将空间分为两份,将存活的对象移到另外一份,标记完后,将原来的空间清除,这样的话空间是连续的,并且效率较高。

特点:空间连续无碎片化、清除高效;

缺点:

压缩一半空间,垃圾清楚的时候一半空间不可用。

对存活对象较多的老年代下,交率较差。

标记-整理算法(Mark-Compact)

由于复制算法的高效性是建立在存活对象少,垃圾对象多的前提下的,对于新生代来说比较适合,但是针对老年代来说,很多对象是一直存活的,所以就不能用复制算法,这样会导致每次回收的垃圾很少,会造成大量的复制。所以标记-整理算法主要是针对老年代来设计的。其原理主要是:分为两个阶段,第一个阶段与标记-清理算法一样,先从根节点标记哪些是被对象引用的,第二阶段将所有存活的对象压缩移动到内存的另一端,按顺序排放,最后清除所有边界以外的空间。

注意:在JDK8默认的配置下使用 新生代,老年代的垃圾回收策略,新生代区域使用标记-复制算法,老年代区域使用标记-整理算法。

三种算法的对比?

对比名称

标记-清除

标记-整理

标记-复制

速度

中等

最慢

最快

空间开销

少(会产生碎片)

少(不会产生碎片)

需要对象2倍大小

移动对象

分代收集算法(Generational Collection)

背景:由于每个收集的算法都没办法符合所有的场景,就好比每个对象所在的内存阶段不一样,被回收的概率也不一样,比如在新生代,基本可以说90%以上的都会被回收,而到老年代接近一半以上的对象则是一半存活的,所以针对这两种不同的场景,回收的策略肯定有所不一样,所以引发而出的就是分代收集算法,根据新生代和老年代不同的场景而用不同的算法,比如新生代用复制算法,而老年代则用标记-整理算法。

HotSpot基于分代的概念,GC所使用的内存回收算法是根据新生代和老年代的特点。

新生代(Yong Gen)

年轻代特点:区域相对老年代较小,对象生存周期短,存活率低,回收频繁。所以适合-标记-复制算法;

老年代(Tenured Gen)

老年代特点:区域较大,对象生命周期长、存活率高,回收不频繁,所以更适合-标记-整理算法;

像CMS、G1这些垃圾收集器都属于这个分代思想演化而来。

增量收集算法(Incremental Collecting)

现有的所有的算法都可能会导致停机-Stop the World的状态,这样会导致所有程序都被挂起,这样会影响用户体验和系统的稳定性。所以增加增量收集算法就是解决每次GC的时候导致停机的问题,其思想是每次垃圾收集中对某一个区域进行收集,再切到用户的线程,直接垃圾收集完成。这样可以很好的避免每次一回收会对整个新生代或老年代进行收集,导致停机场景。

优点:避免停机问题(Stop the World)

缺点:新的垃圾不断产生,会导致线程不断切换上下文,导致回收垃圾成本上升,导致系统吞吐量的下降;

分区收集算法

分区算法主要是通过将整个堆空间划分成连线不同的小区间,每一个区间都可以独立使用,独立回收。这样的话可以通过算法来控制每次对垃圾回收的是哪几个分区,不会因为每次一回收把某个大区都回收了,降低停机概率。

优点:避免停机和合理回收特定分区;

缺点:算法较复杂,需要动态计算每次回收的分区;

最后

垃圾收集算法,是垃圾收集器的核心,年轻代、老年代不同的场景可以针对不同的收集算法,针对JVM来说,对象是有生命周期的,当然JDK8默认的收集器是CMS新生代区域使用标记-复制算法,老年代区域使用标记-整理算法。但随着分代收集算法、分区算法等出现,可以预见不久的将来将会出现很少需要stop the word 或者甚至不停机的垃圾手机器。

参考文献:

《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》

https://blog.csdn.net/luzhensmart/article/details/81431212

https://blog.csdn.net/weixin_42128166/article/details/80184449

https://blog.csdn.net/weixin_44046437/article/details/99686843

https://blog.csdn.net/baidu_37107022/article/details/89277790

https://blog.csdn.net/wuzhiwei549/article/details/80563134

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术趋势 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档