一、如何判断一个对象的死亡
1、一个对象的死亡
当一个对象已经不再被任何的存活对象继续引用时(即没人用),就可以宣判为已经死亡,判断对象存活的算法一般有两种:引用计数算法和可达性分析算法。
2、什么是引用计数器
引用计数器算法(Reference Counting)比较简单,对每个对象保存一个整形的引用计数器属性。用于记录对象被引用的情况。
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效后,引用计数器就减1。
只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。
3、该算法的优缺点
优点:
(1)实现简单,垃圾对象便于辨识;判断效率高,回收没有延迟性。
缺点:
(1)它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
(2)每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
(3)引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。
4、什么是循环引用
(1)创建对象a,存储在堆中,main方法的栈帧中的局部变量a引用了堆空间Test01实例a地址。
(2)创建对象b,存储在堆中,main方法的栈帧中的局部变量b引用了堆空间Test01实例b地址。
(3)Test01实例a的obj引用了Test01实例b地址。
(4)Test01实例b的obj引用了Test01实例a地址。
5、循环引用会有什么问题
当执行a=null; b=null; 时,就变成如图,将会导致堆循环相互引用,从而导致gc回收失败。
二、Java是否采用了引用计数算法
1、例子
package deathAlgorithm;public class Test01 { private Test01 obj = null; byte[] bytes = new byte[2*1024*1024]; public static void main(String[] args) { //一:直接引用 //a引用实例a,所以实例a引用数为1 Test01 a = new Test01(); //b引用实例b,所以实例b引用数为1 Test01 b = new Test01(); //二:相互引用 //a.obj引用了实例b,故实例b引用数+1=2 a.obj = b; //b.obj引用了实例a,故实例a引用数+1=2 b.obj = a; //三:删除引用 a = null; b = null; //四:垃圾清除,如果采用引用算法的话,会导致内存无法释放 System.gc(); }}
2、设置参数
-Xms30M
-Xmx30M
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
执行结果:
{Heap before GC invocations=1 (full 0): PSYoungGen total 9216K, used 6125K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 74% used [0x00000000ff600000,0x00000000ffbfb6c8,0x00000000ffe00000) from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 20480K, used 0K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000) object space 20480K, 0% used [0x00000000fe200000,0x00000000fe200000,0x00000000ff600000) metaspace used 3251K, capacity 4496K, committed 4864K, reserved 1056768K class space used 352K, capacity 388K, committed 512K, reserved 1048576K2022-02-03T16:40:57.367+0800: [GC (System.gc()) [PSYoungGen: 6125K->776K(9216K)] 6125K->784K(29696K), 0.0012983 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 9216K, used 776K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffe00000) from space 1024K, 75% used [0x00000000ffe00000,0x00000000ffec2020,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 20480K, used 8K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000) object space 20480K, 0% used [0x00000000fe200000,0x00000000fe202000,0x00000000ff600000) metaspace used 3251K, capacity 4496K, committed 4864K, reserved 1056768K class space used 352K, capacity 388K, committed 512K, reserved 1048576K}{Heap before GC invocations=2 (full 1): PSYoungGen total 9216K, used 776K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffe00000) from space 1024K, 75% used [0x00000000ffe00000,0x00000000ffec2020,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 20480K, used 8K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000) object space 20480K, 0% used [0x00000000fe200000,0x00000000fe202000,0x00000000ff600000) metaspace used 3251K, capacity 4496K, committed 4864K, reserved 1056768K class space used 352K, capacity 388K, committed 512K, reserved 1048576K2022-02-03T16:40:57.368+0800: [Full GC (System.gc()) [PSYoungGen: 776K->0K(9216K)] [ParOldGen: 8K->617K(20480K)] 784K->617K(29696K), [metaspace: 3251K->3251K(1056768K)], 0.0031266 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] Heap after GC invocations=2 (full 1): PSYoungGen total 9216K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 20480K, used 617K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000) object space 20480K, 3% used [0x00000000fe200000,0x00000000fe29a700,0x00000000ff600000) metaspace used 3251K, capacity 4496K, committed 4864K, reserved 1056768K class space used 352K, capacity 388K, committed 512K, reserved 1048576K}Heap PSYoungGen total 9216K, used 166K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) eden space 8192K, 2% used [0x00000000ff600000,0x00000000ff629aa0,0x00000000ffe00000) from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen total 20480K, used 617K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000) object space 20480K, 3% used [0x00000000fe200000,0x00000000fe29a700,0x00000000ff600000) metaspace used 3272K, capacity 4496K, committed 4864K, reserved 1056768K class space used 354K, capacity 388K, committed 512K, reserved 1048576K
gc前年轻代使用了used 6125K,gc后年轻代使用了used 776K,说明Java没有使用引用计数算法
三、什么是可达性分析算法
1、可达性分析算法
(1)可达性分析算法也称为引用链法(GCRoots),它的核心目的就是判断java对象是否存活?
(2)相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
2、什么是GCRoots
所谓“GCRoots”,或者说tracingGC的“根集合”就是一组必须活跃的引用。
3、什么是可达性分析
(1)基本思路就是通过一系列名为“GCRoots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GCRoots没有任何引用链相连时,则说明此对象不可用。
(2)也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,没有被遍历到的就自然被判定为不可达(暂时处于“缓刑”阶段)。