Java 软引用和弱引用,垃圾回收算法
在Java中,虽然不需要程序员手动去管理对象的生命周期
但是如果希望某些对象具备一定的生命周期的话
但是又要避免OOM,比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误
当遇到这种错误如何避免,如何有效地解决这个问题呢?
需要用到软引用和弱引用了
强引用、软引用、弱引用、虚引用的概念
从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用
Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。
![](https://img.haomeiwen.com/i3248447/e477071d0e5e65ce.png)
1.强引用(StrongReference)
强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:
Object object = new Object();
String str = "hello";
2.软引用(SoftReference)
软引用是用来描述一些有用但并不是必需的对象
在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
在Java中用java.lang.ref.SoftReference类来表示
对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象
因此,这一点可以很好地用来解决OOM的问题
并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等
3.弱引用(WeakReference)
弱引用也是用来描述非必需对象的
当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
在java中,用java.lang.ref.WeakReference类来表示
4.虚引用(PhantomReference)
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期
在java中用java.lang.ref.PhantomReference类表示
如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
垃圾回收原理分析
为什么要进行垃圾回收?
前面已经说过了有OOM的问题,因为内存不是无限大的
而我们一直在占用内存,而不释放那么总有某个时候内存会被消耗完
所以必须要进行垃圾回收
那什么样的对象可以当作是垃圾?垃圾什么时候被回收?
垃圾我们的理解就是没有用处的了,没有被使用的对象
那么如何找到这些对象
判断对象是否是垃圾有两种算法:
- 引用计数法
给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的
Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况
- GC Roots可达分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的
回收分为两步:
- 将需要回收的对象加入回收队列
- 调用对象的 finalize() 方法
GC Roots不可达的对象并非是“非死不可”的
若要宣判一个对象死亡,至少需要经历两次标记阶段
如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,如把this关键字赋值给其他变量,将会从回收队列中移除
如何判断无用的类呢?需要满足以下三个条件
-
该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
-
加载该类的ClassLoader已经被回收。
-
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
回收算法有:
- 标记清除算法
效率低,节省一些内存
标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作
- 复制算法
效率要高,浪费一些内存
它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉
-
标记整理算法
让所有存活对象都向一端移动,然后直接清理掉边界以外的内存 -
分代收集算法
设计GC分代,对象有年龄,新生代,老年代,永久代,不同代算法不同
根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法
没有最好的垃圾收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器