JVM垃圾收集中判断对象存活相关问题
如何判断对象是否存活?
1.1. 引用计数算法
引用计数算法基本思路:
- 在对象内部,添加并维护一个引用计数器;
- 每当有一个地方引用它的时候,计数器就加 +1;
- 每当有一个引用失效的时候,计数器就减 -1;
- 当计数器的值为 0 的时候,那么该对象就是可被 GC 回收的垃圾对象
注:引用计数算法存在的问题:对象循环引用
a 对象引用了 b 对象,b 对象也引用了 a 对象,a、b 对象却没有再被其他对象所引用了,其实正常来说这两个对象已经是垃圾了,因为没有其他对象在使用了,但是计数器内的数值却不是 0,所以引用计数算法就无法回收它们。
1.2. 可达性分析算法
可达性分析算法(Reachability Analysis)基本思路:通过定义了一系列称为 “GC Roots” 的根对象作为起始节点集,从 GC Roots 开始,根据引用关系往下进行搜索,查找的路径我们把它称为 “引用链”。当一个对象到 GC Roots 之间没有任何引用链相连时(对象与 GC Roots 之间不可达),那么该对象就是可被 GC 回收的垃圾对象。
可达性分析算法也是 JVM 默认使用的寻找垃圾算法。
例如:
Object 6、Object 7、Object 8 彼此之前有引用关系,但是没有与 “GC Roots” 相连,那么就会被当做垃圾所回收。
1.3. Java 中的四种引用类型
1.3.1. 强引用(Strong Reference)
强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足时,JVM 宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
Object strongReference = new Object();
如果强引用对象不使用时,需要弱化从而使 GC 能够回收。
弱化方式 1:显式地设置 strongReference 对象为 null,则 gc 认为该对象不存在引用,这时就可以回收这个对象。但是,具体什么时候收集这要取决于 GC 算法。例如,strongReference 是全局变量时,就需要在不用这个对象时赋值为 null,因为强引用不会被垃圾回收。
strongReference = null;
应用场景:在 ArrayList 集合类中定义 elementData 数组,在调用 clear () 方法清空集合元素时,将每个数组元素被赋值为 null 。目的是为了将内存数组中存放的引用类型进行内存释放,可以及时释放内存。不选择将 elementData=null,是为了避免在后续调用 add () 等方法添加新元素时,需要进行内存的重新分配。
public void clear() {modCount++;// clear to let GC do its workfor (int i = 0; i < size; i++)elementData[i] = null;size = 0;
}
弱化方式 2:让对象超出作用域范围。
应用场景:在一个方法的内部有一个强引用,这个引用保存在 VM Stack 栈中(GC Root ),而真正的引用对象 (Object) 保存在堆中。当这个方法运行完成后,就会退出方法栈,则这个对象会被回收。
public void test() {Object strongReference = new Object();// 省略其他操作
}
1.3.2. 软引用(Soft Reference)
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。所以,软引用可用来实现内存敏感的高速缓存。
创建软引用,可以使用 SoftReference:
// 强引用
String strongReference = new String("abc");// 软引用
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<String>(str);// 访问软引用
softReference.get();
软引用对象是在 jvm 内存不够的时候才会被回收,我们调用 System.gc () 方法只是起通知作用,最终何时回收,由 JVM 决定。
所以,当内存不足时,JVM 首先将软引用中的对象引用置为 null,然后通知垃圾回收器进行回收:
// 软引用
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<>(str);str = null;// Notify GC
System.gc();try {byte[] buff1 = new byte[900000000]; // 内存充沛// byte[] buff2 = new byte[900000000]; // 内存不足
} catch (Error e) {e.printStackTrace();
}System.out.println(softReference.get()); // abc 或 null
应用场景:短视频 APP 中的视频缓存,后退时,显示的短视频内容是重新进行请求还是从缓存中取出呢?
- 如果一个短视频在播放结束时,就进行内容的回收,则后退查看前面播放的短视频时,需要重新请求。
- 如果将播放过的短视频存储到内存中,会造成内存的开销,甚至会造成内存溢出。
此时,可以使用软引用解决这个实际问题:
// 获取视频播放器对象
Player videoAlayer = new Player();// 加载短视频
Video video = audioAlayer.getVideo();// 将播放完毕的短视频设置为软引用
SoftReference softReference = new SoftReference(video);// 回退或者再次播放时
if(softReference.get() != null) {// 内存充足,还没有被回收器回收,直接获取缓存video = softReference.get();
} else {// 内存不足,软引用的对象已经回收video = audioLayer.getVideo();// 重新构建软引用softReference = new SoftReference(video);
}
软引用测试1(内存充足时):
public class Test4 {public static void main(String[] args) {String str1=new String("elysia");//软引用//str2=str1SoftReference<String> str2=new SoftReference<>(str1);//弱化强引用str1=null;//Notify GCSystem.gc();try {byte[] buff=new byte[900000000];//内存充沛
// byte[] buff1=new byte[900000000];//内存充沛
// byte[] buff2=new byte[900000000];//内存充沛
// byte[] buff3=new byte[900000000];//内存不足}catch (Error error){error.printStackTrace();}//内容充足时,GC不会回收,可以正常访问//内容不足时,GC进行回收,不能正常访问,返回nullSystem.out.println(str2.get());}
}
软引用测试2(内存不足时):
public class Test4 {public static void main(String[] args) {String str1=new String("elysia");//软引用//str2=str1SoftReference<String> str2=new SoftReference<>(str1);//弱化强引用str1=null;//Notify GCSystem.gc();try {byte[] buff=new byte[900000000];//内存充沛byte[] buff1=new byte[900000000];//内存充沛byte[] buff2=new byte[900000000];//内存充沛byte[] buff3=new byte[900000000];//内存不足}catch (Error error){error.printStackTrace();}//内容充足时,GC不会回收,可以正常访问//内容不足时,GC进行回收,不能正常访问,返回nullSystem.out.println(str2.get());}
}
1.3.3. 弱引用(Weak Reference)
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
创建弱引用,使用 WeakReference:
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;System.gc();// 一旦发生GC,弱引用一定会被回收
System.out.println(weakReference.get());
弱引用测试1(没有回收时):
public class Test4 {public static void main(String[] args) {String str1=new String("elysia");//弱引用WeakReference<String> str2=new WeakReference<>(str1);//弱化强引用str1=null;//此时没有进行回收System.out.println(str2.get());}
}
弱引用测试2(进行回收后):
public class Test4 {public static void main(String[] args) {String str1=new String("elysia");//弱引用WeakReference<String> str2=new WeakReference<>(str1);//弱化强引用str1=null;//Notify GCSystem.gc();//进行回收后System.out.println(str2.get());}
}
1.3.4. 虚引用(Phantom Reference)
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,完全不会对其生存时间构成影响,它就和没有任何引用一样,随时可能会被回收。
虚引用,主要用来跟踪对象被垃圾回收的活动,可以在垃圾收集时收到一个系统通知。
在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get () 方法,而且它的 get () 方法仅仅是返回一个 null,也就是说将永远无法通过虚引用来获取对象。
public class PhantomReference<T> extends Reference<T> {public T get() {return null;}
}
案例:
public class Test4 {public static void main(String[] args) {String str1=new String("elysia");//虚引用PhantomReference<String> str3=new PhantomReference<>(str1, new ReferenceQueue<>());System.out.println(str3.get());}
}