一、判断对象是否死亡算法
有以下两种算法判断对象实例是否死亡:
1、引用计数算法:给每个对象添加一个引用计数器,当有对象引用时加1,当引用失效时减1,任何引用计数器为0的对象实例就是不可能再被使用的——对象实例死亡。但它无法解决对象相互引用的情况。
2、可达性分析算法:通过一系列被称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则说明此对象不能再被使用——对象实例已死亡。可作为GC Roots的对象包括:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区常量引用的对象,本地方法栈中本地方法引用的对象。
二、引用的种类
1、强引用:类似“Object a = new Object();”的引用,只要引用还存在就不会被垃圾搜集器回收
2、软引用:用来描述有用但非必需的对象,在系统内存不足时会对软引用对象进行标记,然后进行垃圾回收,若此次回收内存依旧不足,将会进行第二次垃圾回收对软引用对象进行回收,下面看看实例
/*设置Java参数
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-Xloggc:./gc.log
-Xms12M
-Xmx12M
*/
public class SoftReferenceTest{
class Teather {
private byte[] name = new byte[7*1024*1024];
}
class Student {
private byte[] name = new byte[2*1024*1024];
}
public static void main(String[] args) throws InterruptedException {
Teather teather = new Teather();
SoftReference softReference1 = new SoftReference<Student>(new Student());
SoftReference softReference2 = new SoftReference<Student>(new Student());
System.gc();
}
}
执行main方法,看gc日志如下
Java HotSpot™ 64-Bit Server VM (25.231-b11) for windows-amd64 JRE (1.8.0_231-b11), built on Oct 5 2019 03:11:30 by “java_re” with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16682356k(2892616k free), swap 33362812k(14016192k free)
CommandLine flags: -XX:InitialHeapSize=12582912 -XX:MaxHeapSize=12582912 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
2019-12-25T16:21:49.806+0800: 0.901: [GC (Allocation Failure) [PSYoungGen: 2857K->510K(3584K)] 2857K->1220K(11776K), 0.0699781 secs] [Times: user=0.03 sys=0.00, real=0.07 secs]
2019-12-25T16:21:49.995+0800: 1.090: [Full GC (Ergonomics) [PSYoungGen: 2565K->374K(3584K)] [ParOldGen: 7878K->8154K(8192K)] 10443K->8529K(11776K), [Metaspace: 2943K->2943K(1056768K)], 0.0824955 secs] [Times: user=0.05 sys=0.00, real=0.08 secs]
2019-12-25T16:21:50.078+0800: 1.173: [Full GC (Ergonomics) [PSYoungGen: 2422K->2422K(3584K)] [ParOldGen: 8154K->8154K(8192K)] 10577K->10577K(11776K), [Metaspace: 2943K->2943K(1056768K)], 0.0219196 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
2019-12-25T16:21:50.100+0800: 1.195: [Full GC (Allocation Failure) [PSYoungGen: 2422K->374K(3584K)] [ParOldGen: 8154K->8141K(8192K)] 10577K->8515K(11776K), [Metaspace: 2943K->2943K(1056768K)], 0.0489998 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
2019-12-25T16:21:50.149+0800: 1.244: [Full GC (System.gc()) [PSYoungGen: 2422K->2422K(3584K)] [ParOldGen: 8141K->8141K(8192K)] 10563K->10563K(11776K), [Metaspace: 2943K->2943K(1056768K)], 0.0045867 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 3584K, used 2484K [0x00000000ffc00000, 0x0000000100000000, 0x0000000100000000)
eden space 3072K, 80% used [0x00000000ffc00000,0x00000000ffe6d100,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8192K, used 8141K [0x00000000ff400000, 0x00000000ffc00000, 0x00000000ffc00000)
object space 8192K, 99% used [0x00000000ff400000,0x00000000ffbf3490,0x00000000ffc00000)
Metaspace used 2950K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 318K, capacity 388K, committed 512K, reserved 1048576K
可以看到eden、from、to和ParOldGen分配大小分别为3M、0.5M、0.5M和8M
a. 在第一次Full GC时,可以看到[ParOldGen: 7878K->8154K(8192K)],说明创建Teather对象实例teather时因eden没有足够内存,直接进入老年代(ParOldGen),因为teather实例为强引用所以未被回收,年轻代[PSYoungGen: 2565K->374K(3584K)],说明softReference1软引用的student被创建;
b. 第二次Full GC时(尝试创建softReference2软引用的student,内存不够发起的内存优化的垃圾收集),年轻代[PSYoungGen: 2422K->2422K(3584K)],大小没有变化,此时softReference1软引用的student被标记,内存依旧不够;
c. 第三次Full GC时,看到 [PSYoungGen: 2422K->374K(3584K)],说明把被标记的softReference1软引用的student被标记回收了;
d. 第四次Full GC(由System.gc()主动触发),[PSYoungGen: 2422K->2422K(3584K)],年轻代回收之后大小没有变化,softReference2软引用的student被标记。
3、弱引用:也是用来描述那些非必需对象,但这些对象的存活时间只能在下次垃圾回收之前
4、虚引用:最弱的一种引用关系,一个对象是否存在虚引用完全不会对其的生存时间造成影响,也无法通过虚引用来获得一个对象实例。设置虚引用的唯一目的在对象被回收时收到一条系统通知。
三、对象最后的“挣扎”——finalize()方法
即使在可达性分析算法中不可达的对象,也不一定会被直接回收,一个对象实例是否被回收准确来说需要进行两次判断:
对象实例经过可达性分析算法分析;对象实例如果发现没有与GC Roots相连的引用链,会判断对象是否有必要执行finalize()方法,如果实例已触发过finalize()方法或者对象实例没有finalize()方法就直接回收,若对象实例有必要执行finalize()方法,会把这个对象实例放入一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的低优先级的Finalizer线程执行它。如下例:
/*设置Java参数
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-Xloggc:./gc.log
-Xms12M
-Xmx12M
*/
public class FinalizeGCTest{
class Teather {
private byte[] name = new byte[7*1024*1024];
}
class Student {
private byte[] name = new byte[2*1024*1024];
@Override
protected void finalize(){
System.out.println("save myself");
}
}
public static void main(String[] args) throws InterruptedException {
Teather teather = new Teather();
WeakReference<Student> weakReference = new WeakReference<>(new Student());
System.gc();
// 因为finalize交由Finalizer这个低优先级线程执行,这里等待1秒
Thread.sleep(1000);
System.gc();
}
}
执行mian主函数,控制台打印出“save myleft”,说明Student的finalize()方法被执行,并出现下面GC日志
Java HotSpot™ 64-Bit Server VM (25.231-b11) for windows-amd64 JRE (1.8.0_231-b11), built on Oct 5 2019 03:11:30 by “java_re” with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16682356k(5408852k free), swap 33362812k(19377284k free)
CommandLine flags: -XX:InitialHeapSize=12582912 -XX:MaxHeapSize=12582912 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
2020-01-02T13:48:30.101+0800: 0.222: [GC (Allocation Failure) [PSYoungGen: 2855K->511K(3584K)] 2855K->1269K(11776K), 0.0019742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2020-01-02T13:48:30.164+0800: 0.285: [Full GC (Ergonomics) [PSYoungGen: 2591K->329K(3584K)] [ParOldGen: 7926K->8156K(8192K)] 10517K->8485K(11776K), [Metaspace: 3274K->3274K(1056768K)], 0.0076949 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2020-01-02T13:48:30.172+0800: 0.293: [Full GC (System.gc()) [PSYoungGen: 2377K->2377K(3584K)] [ParOldGen: 8156K->7991K(8192K)] 10533K->10369K(11776K), [Metaspace: 3275K->3275K(1056768K)], 0.0054349 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
2020-01-02T13:48:31.177+0800: 1.298: [Full GC (System.gc()) [PSYoungGen: 2500K->327K(3584K)] [ParOldGen: 7991K->7894K(8192K)] 10491K->8222K(11776K), [Metaspace: 3286K->3286K(1056768K)], 0.0122342 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 3584K, used 358K [0x00000000ffc00000, 0x0000000100000000, 0x0000000100000000)
eden space 3072K, 11% used [0x00000000ffc00000,0x00000000ffc59b08,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8192K, used 7894K [0x00000000ff400000, 0x00000000ffc00000, 0x00000000ffc00000)
object space 8192K, 96% used [0x00000000ff400000,0x00000000ffbb5b78,0x00000000ffc00000)
Metaspace used 3292K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 353K, capacity 388K, committed 512K, reserved 1048576K
从日志可以看出,第一次Full GC (System.gc())未能将Studen实例回收,此时触发执行finalize()方法,休眠一秒后,再次Full GC (System.gc()),此时finalize()被执行了,直接回收了Studen实例。