1. 什么是JavaGC
Java GC(Garbage Collection,垃圾回收)机制,顾名思义,就是Java将程序中不再需要使用的对象进行回收。目的是释放内存,防止内存泄漏的发生。Java的垃圾回收机制也是和其他面向对象语言(例如C++)的区别之一,Java可以自己实现对无效对象的处理,无需开发人员随时注意什么时候去销毁对象。
在GC这件事上,Java已经做的很棒了,但我们可以做到更棒,当我们理解了怎么定义垃圾,怎么回收垃圾以及如何监控和优化GC,极大程度上可以帮助我们在日常工作中排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高效的程序
2. 怎样定义垃圾
要做到垃圾回收,首先就要确认哪些是垃圾,即哪些对象是不再需要使用或者是长时间不再使用的,Java通过一系列算法来确定这些对象。
2.1 引用计数算法
引用计数的思想是在对象的头里分配一个空间来存储该对象被引用的次数,每被引用一次就+1,失去一个引用就-1,当该对象被引用次数为0的时候,就是该对象被kill的时候了。
引用删除
jvm启动参数 -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -XX:SurvivorRatio=8
从上图可见,当一个引用被删除时,GC就会将它的内存进行回收。
但问题来了,当两个对象循环引用的时候会发生什么呢?
假如上图中设置
byte[] b2 = b1;
就会发现,即使b1=null;
也是不能被回收掉的。
2.2 可达性分析算法
在主流的编程语言里,都使用可达性分析算法来判断对象是否存活,可达性分析算法的基本思路是,通过一些被称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
这时,即可解决了引用计数算法不能处理循环引用的问题了。
3. GC 算法
3.1 标记/清除算法
标记/清除算法如题,分为标记和清除两个阶段进行。
标记:即使用可达性分析算法,遍历GC Roots对象,将所有可达对象都打上标记
清除:对堆内存进行遍历,将所有没有被标记的对象全部回收
标记/清除算法存在问题:
1、效率低下。因为要遍历内存中的对象,所以标记和清除两个阶段的效率都不高,而且GC时需要停止应用程序,虽然不是连续的,但是当对象极多时,也并不好用
2、空间问题。标记清除是对没有标记的对象进行回收,所以回收后之后会产生大量不连续的内存碎片,内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作
3.2 复制算法
复制算法的原理是:将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。
复制算法每次都是对整个半区进行内存回收,这样就减少了标记对象遍历的时间,在清除使用区域对象时,不用进行遍历,直接清空整个区域内存,而且在将存活对象复制到保留区域时也是按地址顺序存储的,这样就解决了内存碎片的问题,在分配对象内存时不用考虑内存碎片等复杂问题,只需要按顺序分配内存即可。
复制算法简单高效,优化了标记/清除算法的效率低、内存碎片多的问题。但是它的缺点也很明显:
1、将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;
2、如果对象的存活率很高,极端一点的情况假设对象存活率为100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的
3.3 标记/整理算法
标记/整理算法从名字上看,与标记/清除算法很像,事实上,标记/整理算法的标记过程与标记/清除算法几乎一样,但标记后不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存
这样,就可以避免清除时需要再次遍历对象的问题,并且将分散的内存片段也置为了连续的内存片段
End
以上,参考《深入理解Java虚拟机》
网友评论