有彩蛋哦!!!(或者公众号内点击网赚获取彩蛋)
深入理解Java虚拟机 第二版 周志明著
根搜索算法解决的是垃圾搜集的基本问题,也就是上面提到的第一个问题,也是最关键的问题,就是哪些对象可以被回收。
不过垃圾收集显然还需要解决后两个问题,什么时候回收以及如何回收。在根搜索算法的基础上,现代虚拟机的实现当中,垃圾搜集的算法主要有三种,分别是标记-清除算法、复制算法、标记-整理算法。这三种算法都扩充了根搜索算法,不过它们理解起来还是非常好理解的。
根索引算法能很好地解决我们应该回收那些对象的问题,但是不能承担垃圾搜集的重任,因为我们运行在JVM上的程序在运行期间如果想进行垃圾回收,就必须然GC线程与程序中的线程配合,才能在不影响程序运行的前提下,顺利的将垃圾进行回收。 所以为了解决这个问题,标记-清除算法产生了。
当堆中的有效内存(avaliable memory)被耗尽的时候,就会停止整个程序(也就是所谓的stop the world)
标记:遍历所有的GC Roots,然后将所有的GC Roots可达的对象标记为存活的对象(下文mark=1已标记,mark=0未标记)
清除:清除的过程将遍历所有堆中的对象,将没有标记的对象(mark=0)全部清除掉
总结:在程序运行过程中,当可用内存被消耗待尽的时候,GC线程就会被处罚并将程序暂停,随后将GC Roots可达的对象标记为1,最后将堆中所有没有标记为1的对象全部清除掉,恢复程序运行,GC Roots可达的对象标记重新置为0;
流程示意图
1.内存即将耗尽时堆中对象的情况
2.标记完之后对象状态
3.清除完之后
为什么必须要停止掉程序呢?
试想一个场景上图最右边的对象a,当内存即将耗尽而且标记过程完了之后,此时程序不停止掉,创建了一个g对象,a能达到g 那么g不应该清除掉,然后标记-清除算法继续执行清除工作,g象的mark=0会被清除待。啊哦,刚创建的g对象变成了null什么鬼
缺点:
效率低(递归与全堆对象遍历),而且在进行GC的时候,需要停止引用程序,这会导致用户体验非常差劲,尤其对于交互式的应用程序来说,简直是灾难。试想一下,你逛了3分钟淘宝,然后就挂了,那电子商务就不会发展到现在了
清理出来的空闲内存是不连续的,怎么来理解一下呢,就是我们“死亡”的对象会随机的出现在内存的各个角落里,把它们清除了之后,内存的布局自然会是不连续的。为了应付这个问题,JVM就必须维持一个内存的空闲列表,当分配一个比较的数组时(大到没有连续的内存空间)
简介:
复制算法将内存分为两个区间,这两个区间是动态的,在任意一个时间点,所有分配的对象内存只能在其中一个区间(活动区间),另外一个区间就是空闲区间
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址一次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。这个时候空闲内存已经变成了活动区间,垃圾对象全部在原来的活动区间,清理掉垃圾对象,原活动区间就变成了空闲区间。
如何做的?
内存即将耗尽,将活动区域GC Roots可达的对象,按顺序排列到空闲区域,将原来的对象地址修改成排列之后的地址;活动区域所有对象已经变成垃圾对象清除,变成新空闲区域,原空闲区域变成新活动区域。
流程图:
1.内存即将耗尽为复制之前状态
2.复制之后
优点:
很好地解决了标记-清除算法,内存布局混乱的缺点
缺点:
浪费一半的内存
假设对象存活率为100%,那么复制算法的GC过程就是重复的把对象复制一遍,而且将所有的引用地址重置一遍。可以预见的复制所消耗的时间随着对象存活率达到一定程度将会变成灾难。所以复制算法使用的场景是可以忍受只是用50%内存,对象存活率非常低
简介:
标记-整理算法分为两个阶段:1.遍历GC Roots可达的对象,标记为已存活,如果不可达标记为未存活;2.将已存活的对象按照顺序排列在内存中,修改新的地址,将末端内存以后的对象清除掉
流程示意图:
1.即将进行GC对象状态和标记之后对象流程图和标记-清除算法是一样的
2.整理之后
优点:
弥补了标记-清除算法,内存区域分散的缺点
弥补了复制算法内存减半的代价
缺点:
效率不高,对于标记-清除而言多了整理工作,对于复制算法而言多了标记工作
标记-清除/复制/标记-整理算法
三个算法都是基于根搜索算法去判断一个对象是否应该被回收,而支撑根搜索算法可以正常工作的理论依据,就是语法中变量作用域的相关内容,因此,要想防止内存泄漏,最根本的方法就是掌握好变量作用域,而不应该使用前面obj=null类似于c/c++内存管理方式。在GC线程开启时,都需要stop the world
按照功能排名:
效率:复制算法>标记整理>标记清除
内存整齐度:复制=标记整理>标记清除
内存使用率:标记整理=标记清除>复制