基本思路:
+1
-1
0
的时候,那么该对象就是可被GC回收的垃圾对象存在的问题:对象循环引用
a
对象引用了 b
对象,b
对象也引用了 a
对象,a
、b
对象却没有再被其他对象所引用了,其实正常来说这两个对象已经是垃圾了,因为没有其他对象在使用了,但是计数器内的数值却不是 0
,所以引用计数算法就无法回收它们。
又称为根可达性算法
基本思路:
过定义了一系列称为“GC Roots”的根对象作为起始节点集,从 GC Roots 开始,根据引用关系往下进行搜索,查找的路径我们把它称为 "引用链" 。当一个对象到 GC Roots之间没有任何引用链相连时(对象与GC Roots之间不可达),那么该对象就是可被GC回收的垃圾对象。
可达性分析算法也是JVM 默认使用的寻找垃圾算法。
例如:
Object 6
、Object 7
、Object 8
彼此之前有引用关系,但是没有与"GC Roots"相连,那么就会被当做垃圾所回收。
无论上述哪种算法,我们都可以看到引用在其中的重要作用。
强引用是使用最普遍的引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足时,JVM 即使
抛出OOM错误,使程序异常终止,也不会随意回收具有强引用的对象来解决内存不足的问题。
Object strongReference = new Object();
如果强引用对象不使用时,需要弱化从而使GC
能够回收。
弱化方式1
显式地设置strongReference
对象为null
,gc
认为该对象不存在引用,这时就可以回收这个对象。
注意:具体什么时候收集取决于GC
算法, 例如,strongReference
是全局变量时,就需要在不用这个对象时赋值为null
,因为强引用不会被垃圾回收。
strongReference = null;
弱化方式2:
让对象超出作用域范围。在一个方法的内部有一个强引用,这个引用保存在VM Stack
栈中(GC Root
),而真正的引用对象(Object
)保存在堆中。当这个方法运行完成后,就会退出方法栈,则这个对象会被回收。
public void test() {
Object strongReference = new Object();
// 省略其他操作
}
如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。所以,软引用可用来实现内存敏感的高速缓存。
// 软引用
String str = new String("abc");
SoftReferencesoftReference = new SoftReference (str); // 访问软引用
softReference.get();
当内存不足时,JVM
首先将软引用中的对象引用置为null
,然后通知垃圾回收器进行回收:
public class Demo01 {
public static void main(String[] args) {
//强引用
String strongReference = new String("abc");
String str = new String("just do do");
//弱引用,进行保护,内存充足不回收。
SoftReference softReference = new SoftReference<>(str);
str = null;
// Notify GC
System.gc();
try {
byte[] buff1 = new byte[900000000]; // 内存充沛
// byte[] buff2 = new byte[900000000];
// byte[] buff3 = new byte[900000000];
// byte[] buff4 = new byte[900000000]; // 内存不足
} catch (Error e) {
e.printStackTrace();
}
System.out.println(strongReference);
System.out.println(softReference.get()); // just do do 或 null
}
}
内存充足:
内存不足:
上述案例可以看出:强引用与软引用在内存不足时的回收情况。
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
创建弱引用,使用WeakReference
:
public static void main(String[] args) {
String str = new String("abc");
WeakReference weakReference = new WeakReference<>(str);
str = null;
System.gc();
// 一旦发生GC,弱引用一定会被回收
System.out.println(weakReference.get());//结果为null
}
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,完全不会对其生存时间构成影响,它就和没有任何引用一样,随时可能会被回收。
虚引用,主要用来跟踪对象被垃圾回收的活动,可以在垃圾收集时收到一个系统通知。
目前主流JVM虚拟机中的垃圾收集器,都遵循分代收集理论:
按照分代收集理论设计的“分代垃圾收集器”,所采用的设计原则:收集器应该将Java
堆划分成不同的区域,然后将回收对象依据其年龄(年龄即对象经历过垃圾收集过程的次数)分配到不同的区域存储。
如果一个区域中大多数对象都是朝生夕灭(新生代),难以熬过垃圾收集过程的话,把它们集中存储在一起,每次回收时,只关注如何保留少量存活对象,而不是去标记大量将要回收的对象,就能以较低代价回收到大量的空间。
如果一个区域中大多数对象都是难以回收(老年代),那么把它们集中放在一起,JVM
虚拟机就可以使用较低的频率,来对这个区域进行回收。
垃圾收集类型划分:
Partial GC
):没有完整收集整个Java
堆的垃圾收集,其中又分为:Minor GC
/ Young GC
)Major GC
/ Old GC
)Mixed GC
):收集整个新生代和部分老年代的垃圾收集。Full GC
):收集整个Java
堆的垃圾收集。在Java
堆区划分成不同区域后,垃圾收集器才可以针对不同的区域,安排与该区域存储对象存亡特征相匹配的垃圾收集算法:标记-复制算法、标记-清除算法、标记-整理算法等。
实现思路:
分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。
存在的问题:
黑色:可回收对象 蓝色:存活对象 白色:未使用空间
“标记-复制”收集算法简称“复制算法”,为了解决“标记-清除”面对大量可回收对象时执行效率低下的问题。
实现思路:
该算法将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把已使用的空间一次清理掉。
存在的问题:
实现思路:
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向内存空间一端移动, 然后直接清理边界以外的内存,这样清理的机制,不会像标记-清除那样留下大量的内存碎片。
综上所述:
当前虚拟机的垃圾收集都基于分代收集思想,根据对象存活周期的不同,将内存分为几个不同的区域,在不同的区域选择使用合适的垃圾收集算法。
例如:Heap
堆分为新生代和老年代,这样我们就可以根据各个年代的特点,从存活几率和额外的空间中选择合适的垃圾收集算法,