41-JVM-GC(二)
GC的进一步分析
1)当对象存在引用时,是否可能在系统触发GC时被回收?
答案是:可能,需要看引用的类型
因此我们需要了解java中的引用类型,在Java中引用类型有四种,分别是:强引用,软引用,弱引用,虚引用,其中虚引用不常用,
-
强引用(Strong Reference):如“Object obj = new Object()”,这类引用是java中最普遍的,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象,不管内存是否够用。
(此引用引用的对象,生命力最强。(对象不会被GC))
-
软引用(Soft Reference):它用来描述一种可能有用,但不是必须的对象,在系统内存不够用时,这类引用会在垃圾收集器下一次回收垃圾时被回收。
(此引用引用的对象,在内存不足时可能会被GC。)
-
弱引用(Weak Reference):它是用来描述非必须的对象,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集器回收垃圾之前,不论系统内存是否够用,都会回收掉只被弱引用关联的对象。
(此引用引用的对象,在GC执行时可能直接会被销毁(即便是内存充足))
-
虚引用(Phantom Reference):最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。
(用的最少,类似没有引用,主要用于记录对象的销毁)
验证强引用
创建一个容器类
class Container{
Object[] array;
public Container(int cap) {
this.array=new Container[cap];
}
//重写finalize方法
@Override
protected void finalize() throws Throwable {
System.out.println("Container.finalize()");
}
}
创建测试类
public class StrongGCTest {
public static void main(String[] args) {
// 强引用
Container container = new Container(10);
//container=null;//container指向的Container对象不可达,(JVM访问不到)
System.gc();//GC启动以后,GC系统会对内存中的对象进行可达性分析。访问不到则进行标记。
}
}
这样手动GC,对象也不会被回收
验证软引用
创建测试类:
public class Soft GCTest{
public static void main(String[] args) {
//软引用
SoftReference<Container> cReference = new SoftReference<Container>(new Container(10));
// Container c =cReference.get();//这种写法将软引用转为强引用,不推荐
cReference.get();
//System.gc();//GC启动以后,GC系统会对内存中的对象进行可达性分析。访问不到则进行标记。
// 演示内存不足(通过JVM参数分析)
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(new byte[1024*1024]);
}
}
}
通过给list集合中不断添加元素,导致内存不足,对象会被回收,会执行finalize()方法
验证弱引用
public class WeakGCTest {
public static void main(String[] args) {
WeakReference<Container> weakReference = new WeakReference<Container>(new Container(10));
weakReference.get();
System.gc();//GC启动以后,GC系统会对内存中的对象进行可达性分析。访问不到则进行标记。
}
}
即使内存没有产生不足的情况,一旦触发GC,那么就会回收该弱类型对象,执行finalize()方法
2)当GC系统没有启动时,对象是否可能被回收?
可能,看你对象分配在哪了?(如果是栈里,则不需要启动GC)
这里涉及到对象逃逸
原则:
小对象,未逃逸,可以栈上分配,栈上分配对象的对象,方法调用结束,对象销毁。
小对象,已逃逸,堆上分配,对象回收需要借助GC系统。
那么什么是未逃逸?什么是已逃逸呢?
未逃逸 : (方法外界没有引用指向此对象),可以直接分配在栈上
已逃逸: (方法外部有引用指向此对象),对象分配在堆上
注:JDK8中默认会打开逃逸分析选项,希望未逃逸的小对象分配在栈上,这样可以避免启动GC对对象进行回收。
下面进行测试:
创建一个main方法,用以调用静态方法
public static void main(String[] args) {
for(int i=0;i<100000000;i++) {
doMethod01();
}
}
静态方法如下:
public class TestGC03 {
//-XX:+PrintGCDetails
static byte[] b2;
static void doMethod01() {
//小对象,未逃逸,可以栈上分配,栈上分配对象的对象,方法调用结束,对象销毁。
//byte[] b1=new byte[1];
//小对象,已逃逸,堆上分配,对象回收需要借助GC系统。
b2=new byte[1];//小对象,但逃逸了(方法外部有引用指向此对象),对象分配在堆上
}
}
若是小对象,未逃逸,则不会造成栈内存溢出,方法调用结束,对象销毁。
若是小对象,已逃逸,则会造成堆内存溢出。