GC回收算法

https://www.cnblogs.com/missOfAugust/p/9528166.html

Java语言引入了垃圾回收机制,让C++语言中令人头疼的内存管理问题迎刃而解,使得我们Java狗每天开开心心地创建对象而不用管对象死活,这些都是Java的垃圾回收机制带来的好处。但是Java的垃圾回收机制的核心原理是什么呢?今天我们来聊聊GC回收算法吧。

JVM的GC回收场景很复杂,不是单个算法就可以搞定的,大致可以分为可达性分析算法、标记-清除算法、标记-整理算法、分代回收算法、复制算法。

广场上,女朋友突然跟你闹分手,然后头也不回地一个人走了,留下你一个人站在树下,BGM缓缓响起“雪花飘飘 北风啸啸 天地 一片 苍茫~~~”树叶纷纷落下,这时的你仿佛被夏洛特里的元华附身,成了全世界最悲伤的人。当你沉浸在悲伤不可自拔,旁边的环卫大妈一脸嫌弃看着你“年轻人你挪一下,别挡到我扫地”。

对你没猜错,地上的落叶,就是GC垃圾回收算法的核心--可达性分析算法。

可达性分析算法
轻风乍起,泛黄的树叶纷纷掉下,刚分手的你不禁长叹“叶子的离开是风的追求还是树的不挽留”,当叶子从枝头掉落的那一刻,它跟树就再也没有任何关系。同样的,可达性分析算法的基本思路就是JVM内存中的对象以树的形式管理,我们称之为"GC tree"。GC tree的根节点叫做GC Roots,通过一些列GC Roots为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用了。

图中的对象1、2、3、4不管哪个都可以找到与GC Roots相连的引用链,属于存活对象,而对象5、6、7虽然彼此相互有联系,但是他们到GC Roots是不可达的,所以属于死亡对象。

有哪些对象可以作为GC Roots呢?

虚拟机栈(栈桢中的本地变量表)中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(Native方法)的引用的对象

标记-清除算法(Mark-Sweep) 分手不是马上分
二次标记
当女朋友跟你闹分手,她是真的要跟你分吗?太天真了少年!!!女人都是感性动物,刀子嘴豆腐心,她只是给你判了个死缓,如果你什么都不做,那我没话说,注孤生吧小伙子;如果你态度端正,那你还有得救!

同样的,当GC线程遍历GC tree检测到无用对象的时候,并不是立马人道毁灭,只是先给它做个标记,告诉对象你已经上了枪毙名单。这里是第一次标记。

挽留爱情该如何做?说情话哄她,拉她去心心念念的馆子吃顿好的,又或者去商场给她买向往已久的迪奥999口红······这些套路我就不说了,反正只要能让女朋友开心,什么付出都是值得的。

当对象第一次被标记的时候,GC线程会去检查此对象是否有必要执行finalize()方法。finalize()方法还记得吧?finalize()定义:finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。什么意思?就是意味着如果我们重写该方法的话,那么在GC回收之前该方法会被执行。

但是有两种情况GC线程会认为没有必要去执行。

1.对象没有覆写finalize()的。女友跟你闹分手,你却像没事人一样回家继续撸游戏,小伙子你心可真大。

2.finalize()已经被虚拟机执行过的。女友此时内心OS:上次吵架你送我一只迪奥999赔罪,这次你又送,你就不知道我这段时间一直想买萝卜丁吗?一定是在敷衍我!呵!男人!

生存还是死亡 要爱情还是要自由 That is a question!
如果对象被判定需要执行finalize()方法,那么它将会放置在一个叫F-Queue的队列里面挨个等待执行,对象自我救赎的机会来了!如果对象想在finalize()中成功拯救自己,只要重新与GC Roots建立关联即可,比如把自己赋值给某个类对象或者对象的成员变量,那么在第二次标记的时候它从“即将回收”的集合中中被移除;如果这时它还没有建立关联,那么它这次真的是GG了,我们用一首《凉凉》给它送别吧。

图解:

以上就是标记-清除算法,不过它有两点不足之处:

1.效率问题,标记和清除过程的效率不高。

2.空间问题,标记清除后会产生大量不连续的内存碎片,碎片太多可能会导致以后在程序中需要分配占用较大连续空间的对象(如数组)时,无法找到足够的连续内存而不得不提前触发下另一次垃圾收集动作。

为了解决这些问题,“复制算法”应运而生。

复制算法(Copying) 物种大逃亡 诺亚方舟!
复制算法思路比较简单:将内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存空间满了,就将还活着的对象复制到另外一块,然后再将之前那块内存空间彻底清空。有点像《圣经》里的一个故事:大洪水要来了,生物纷纷逃上诺亚方舟以躲避灾难。这样玩的话每次都是对整个半区进行内存回收,内存分配的时候也不用考虑内存碎片的情况,简单粗暴让人喜欢!只不过这种算法将内存缩小为了原来的一半,代价太高昂了,我们要知道,内存是很宝贵的资源!

黄金比例 8:1:1
医学研究证明,感冒是由病毒引起的。咳咳开个玩笑!软件团队研究表明,内存中的绝大部分对象都是“朝生暮死”的,所以完全没必要非要按照1:1的比例来玩。而是把内存分成了一块较大的Eden区(伊甸区)和两块较小的Survivor区(幸存区),每次都使用伊甸区和其中一块幸存区(我们取个别名叫幸存者1号吧,另外一块取名幸存者2号)。当回收的时候,将伊甸区和幸存者1号区域里面的对象一次性复制到幸存者2号里面,最后对伊甸区和幸存者1号进行清算,里面的所有对象不管生存还是死亡彻底清除干净,比灭霸打个响指还厉害!

图解:

现在HotSpot虚拟机默认伊甸区和两块幸存区的比例大小为8:1:1,这样平时工作的时候,只有10%的内存会被浪费掉,这样是不是很划算呢?

看到这里有人鞋会问,幸存区为什么要分为两块?比例9:1才是最完美吧?NO!NO!NO!,这里不得不引出另一个算法--分代收集算法了。

分代收集算法(Generational Collection) 历经考验 修成正果
有没有发现,所谓的爱情其实都是要经历无数次的磨合,无数次的考验,在一起的两个人只有经受住了这些磨难,才会走进婚姻的殿堂,有了圆满的结局,而经受不住这些考验,那双方就只有各自安好,相忘江湖。

在我们每次GC回收的时候,都会有一小部分对象活下来,然后一直活到下一次GC再次被检测。最近很火的吃鸡游戏,玩家不管用什么手段,刚枪也好苟也好,只求活下去成为最后的幸存者。而Java对象也是这样,经历GC的层层考验,最终成了打不死的小强。这时候该轮到GC不爽了,你丫的每次都浪费我的时间,小强内心OS"就喜欢看你不爽我又干不掉我的样子!",对于这批顽固分子,GC作为执法者决定眼不见为净,于是委托JVM专门划分出一块区域给他们颐养天年,从此天涯是路人。而划分出的这块区域就是赫赫有名的“老年代”了,而与之相对应的就是之前GC频繁的“新生代”。

一个对象该如何从“新生代”跑到“老年代”去呢?

我们创建一个对象,它的对象头里面会有一个GC分代标识,每经历一次GC如果能活下来该标识+1,当加到一定次数后,GC会判定该对象是个老流氓,于是乎把它从“新生代”转移到“老年代”了,安排!具体参考我的另一篇博客《假如Java对象是个人······》

新生代每经历一次GC,幸存者2号区域活下来的对象年龄标识自动+1,然后判断是否满15岁(默认值15次),如果满15岁了,那么就从幸存者2号复制到“老年代”里面取颐养天年,如果没有的话,那么就复制到幸存者1号区域里面去,然后幸存者2号区域被清空。

由于老年代对象存活率极高,用不着复制算法这一套。于是有人提出了另外一种算法叫做“标记-整理算法”。

标记-整理算法(Mark-Compact)
标记-整理算法其实基本过程跟“标记-删除”算法差不多,只不过后续的步骤不是对无用对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理到端边界以外的内存。这样就完美解决了“标记-清除算法”内存碎片化的问题。

图解:

讲到这里,JAVA的GC回收算法基本就差不多了。我们的GC就是针对内存中的不同区域,采取合理的算法从而达到自动清理的效果。新生代的对象大多数朝生暮死,就采用“复制算法”,老年代的对象存活率极高,就采用“标记-删除算法”或者“标记-整理算法”。

现在是不是觉得GC回收算法没有想象中那么神秘?希望我的理解能给你带来一点帮助,由于人懒,图片都是网上直接拿来用的。另外,如果现实中跟女友有摩擦,该服软还是得服软,男人就应该表现得大度一点,毕竟两个人相处不易,更何况她还是将和你共度余生的人,不要因为一时冲动而抱憾终生。额······我仿佛又闻到了爱情的酸臭味!

参考资料:《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》

GC回收算法的更多相关文章

  1. GC回收算法--当女友跟你提分手!

    Java语言引入了垃圾回收机制,让C++语言中令人头疼的内存管理问题迎刃而解,使得我们Java狗每天开开心心地创建对象而不用管对象死活,这些都是Java的垃圾回收机制带来的好处.但是Java的垃圾回收 ...

  2. GC回收算法&&GC回收器

    GC回收算法 什么是垃圾? 类比日常生活中,如果一个东西经常没被使用,那么就可以说是垃圾. 同理,如果一个对象不可能再被引用,那么这个对象就是垃圾,应该被回收. 垃圾:不可能再被引用的对象. fina ...

  3. java虚拟机学习总结之GC回收算法与GC收集器

    GC回收算法 1.标记清除算法分为标记阶段和清除阶段标记阶段:通过特定的判断方式找出无用的对象实例并将其标记清除阶段:将已标记的对象所占用的内存回收缺点:运行多次以后容易产生空间碎片,当需要一整段连续 ...

  4. JVM内存模型及GC回收算法

    该篇博客主要对JVM内存模型以及GC回收算法以自己的理解和认识做以记录. 内存模型 GC垃圾回收 1.内存模型 从上图可以看出,JVM分为 方法区,虚拟机栈,本地方法栈,堆,计数器 5个区域.其中最为 ...

  5. 初步了解JVM第三篇(堆和GC回收算法)

    在<初步了解JVM第一篇>和<初步了解JVM第二篇>中,分别介绍了: 类加载器:负责加载*.class文件,将字节码内容加载到内存中.其中类加载器的类型有如下:执行引擎:负责解 ...

  6. GC垃圾回收算法

    什么是GC垃圾回收呢.日常生活中我们去餐厅吃饭吃完饭,吃完饭走了餐具不用管,服务员在把餐具拿走,这是一种方式,服务员怎么知道他要来把餐具拿走呢,因为你走了,这个位置空了.服务员什么时候拿走餐具很重要, ...

  7. 三:GC回收机制

    jvm垃圾回收机制: jvm中有个垃圾回收线程,它是低优先级的,当虚拟机空闲或堆内存不足时,它就会去清除不可达对象. GC是如何去判断对象是否能被回收的 早期GC判断对象是否能被回收时用的引用计数法, ...

  8. java面试一日一题:java中垃圾回收算法有哪些

    问题:请讲下在java中有哪些垃圾回收算法 分析:该问题主要考察对java中垃圾回收的算法以及使用场景 回答要点: 主要从以下几点去考虑, 1.GC回收算法有哪些 2.每种算法的使用场景 3.基于垃圾 ...

  9. JVM中的垃圾回收算法GC

    GC是分代收集算法:因为Young区,需要回收垃圾对象的次数操作频繁:Old区次数上较少收集:基本不动Perm区.每个区特点不一样,所以就没有通用的最好算法,只有合适的算法. GC的4大算法 1.引用 ...

随机推荐

  1. spring--路由

    @RestController: Spring4之后新加入的注解,原来返回json需要@ResponseBody和@Controller配合. 即@RestController是@ResponseBo ...

  2. curl模拟安卓手机进行网络请求

    $url = "http://somedomain/ver/ffffffff-e31e-85d5-ffff-ffffa6220605/2/en/b0:79:94:e7:99:4a/3.5&q ...

  3. AttributeError: module 're' has no attribute 'search'

    命名py脚本时,不要与python预留字,模块名等相同,即Python文件名不要使用Python系统库的名字,就是因为使用了Python系统库的名字,所以在编译的时候才会产生.pyc文件.正常的Pyt ...

  4. String源码分析(1)--哈希篇

    本文基于JDK1.8,首发于公众号:Plus技术栈 让我们从一段代码开始 System.out.println("a" + "b" == "ab&qu ...

  5. SELinux 服务检查与关闭

    查看SELinux状态: 1./usr/sbin/sestatus -v      ##如果SELinux status参数为enabled即为开启状态 SELinux status:         ...

  6. Idea Failed to read artifact descriptor for xx:jar:unknown

    网上的解决方案: 根据网上说明添加了maven命令clean compile install -Dmaven.test.skip=true,与我遇到的问题不同 有的方法猜测可以通过,但是没时间测试了 ...

  7. Jenkins使用Publish Over FTP Plugin插件上传FTP详解

    一.安装插件[Publish Over FTP] 二.在[系统管理]->[系统设置]->[Publish over FTP]->点击[增加]按钮,增加一个要连接的FTP: FTP S ...

  8. 你还在为移动端选择器picker插件而捉急吗?

    http://www.cnblogs.com/jingh/p/6381079.html 开题:得益于项目的上线,现在终于有时间来写一点点的东西,虽然很浅显,但是我感觉每经历一次项目,我就学到了很多的东 ...

  9. ios实现下载图片的裁减和显示

    使用如下的方法可以裁减的同时保证了不丢失像素. - (void)connectionDidFinishLoading:(NSURLConnection *)connection{    // Set ...

  10. poj 2154 Color 欧拉函数优化的ploya计数

    枚举位移肯定超时,对于一个位移i.我们须要的是它的循环个数,也就是gcd(i,n),gcd(i,n)个数肯定不会非常多,由于等价于n的约数的个数. 所以我们枚举n的约数.对于一个约数k,也就是循环个数 ...