1.如何判断对象已死
1.1引用计数法
主流的JVM中没有选用引用计数器法来管理内存,最主要的原因是引用计数器无法解决对象的循环引用问题
2.可达性分析算法
此算法核心的思想:通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为"引用链",当一个对象到GC Roots没有任何的引用链相连时 证明此对象不可用。
在java语音中,可作为GC Roots的对象包含以下几种:
1.虚拟机栈中引用的对象
2.方法区中静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中引用的对象
2.回收方法区
方法区(永久代)的垃圾回收主要集中两部分内容:废弃常量和无用类
回收废弃常量和回收java堆中的对象十分类似。已常量池中字面量的回收为例,譬如一个字符串"xxx"已经进入了常量池,但是当前系统没有任何一个字符串对象引用常量池中的xxx常量,也没有其他任何地方应用这个字面量,如果此时发生GC并且有必要的话,这个xxx常量会被系统清理出常量池。常量池中的其他类(接口),方法,字段的符号引用类似。判断一个类是否是无用类则相对复杂多。类需要同时满足下面三个条件才会被算是无用类
1.该类的所有实列都已经被回收(java堆中不存在任何该类的实列)。
2.加载该类的ClassLoader已被回收。
3.该类对应的Class对象没有任何其他地方被引用,无法在任何其他地方通过反射访问该方法
3.垃圾回收算法
3.1标记-清除算法
"标记-清除"算法是最基础的回收算法。算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。后续的回收算法都是基于这种思路对其不足加以改进而已。
"标记-清除"算法的不足主要有两个:
1.效率问题:标记和清除这两个过程的效率都不高
2.空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行中需要分配较大对象时,无法找到足够内存而不得不提前触发一次垃圾回收。
3.2 复制算法(新生代回收算法)
复制算法是为了解决标记清除算法的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用掉其中一块。当这块内存需要进行垃圾回收时,会将此区域还存活的对象复制到另外一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对半区进行内存回收,内存分配也就不需要考虑内存碎片等复杂情况。
现在的商用虚拟机都是采用这种收集算法来回收新生代
新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另一个称为To区域)
新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区 一个eden区两个survivor区
大部分对象在Eden区中生成 回收时先将eden区存活对象复制到一个survivor0区 然后清空eden区
当这个survivor0区也存放满了 则将eden区和survivor0区存活的对象复制到另外一个survivor1区 然后清空eden区和survivor0区
此时survivor0区是空的 然后将survivor0区和survivor1区交换 及保持survivor1区为空 如此往复
3.3标记整理算法(老年代回收算法)
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。针对老年代的特点,提出了一种称之为"标记-整理算法"。标记过程仍与标记-清除过程一致,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。
image.png
3.4分代手机算法
当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法













网友评论