Java引用
- StrongReference 强引用
- SoftReference 软引用
- WeakReference 弱引用
- PhantomReference 虚引用
1. StrongReference
介绍
我们平常用的最多的就是强引用了
Object obj = new Object();
这种形式的引用称为强引用
特点
- 强引用所指向的对象在任何时候都不会被系统回收;
- 由于特点1的原因,强引用可能导致内存泄漏。
强引用只有引用置为null,垃圾回收器才会去回收该资源,否则不会去回收,即使在内存不足的情况下系统宁愿报oom(OutOfMemory)错误,也不会去回收强引用资源.
2. SoftReference
当JVM中的内存不足的时候,垃圾回收器会释放那些只被软引用所指向的对象。如果全部释放完这些对象之后,内存还不足,才会抛出OutOfMemory错误。软引用非常适合于创建缓存。
package com.nan.testproject;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
* -Xms256m
* -Xmx2g
*/
public class ReferenceTest {
public static void main(String[] args) {
ReferenceQueue referenceQueue = new ReferenceQueue();
// 软引用对象中指向了一个占用空间为1g的数据
SoftReference softReference =
new SoftReference<>(new int[1024 * 1024 * 1024 / 4], referenceQueue);
// 主动调用一次gc,由于jvm最大堆内存-Xmx设置为2g,此时JVM的内存够用,故softReference引用的对象未被回收
System.gc();
System.out.println(softReference.get());
// 申请占用1g大小的数组,此时JVM内存不足,会导致一次自动的gc,会回收softReference对象中指向的数组对象
int[] datas = new int[1024 * 1024 * 1024 / 4];
SoftReference ref = null;
// 当softReference中数组被回收后,会将引用放入绑定的referenceQueue中,referenceQueue.poll()也就不为null了
while ((ref = (SoftReference) referenceQueue.poll()) != null) {
// softReference中的int数组对象已被回收,此时可以置softReference该强引用为null
// softReference = null;
}
System.out.println(softReference.get());
}
}
输出
[I@75b84c92
null
注意,上面代码创建创建软引用部分,如果代码改为下面部分
int[] data = new int[1024 * 1024 * 1024 / 4];
SoftReference softReference =
new SoftReference<>(data, referenceQueue);
运行会报OOM,如下
[I@75b84c92
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.nan.testproject.ReferenceTest.main(ReferenceTest.java:26)
JVM进行软引用垃圾回收时只会回收被弱引用关联的对象,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象。(弱引用和虚引用也是如此)
3. WeakReference
弱引用相比软引用拥有更短的生命周期,垃圾收集器一旦发现了只具有弱引用的对象,不管当前内存是否足够,都会回收它的内存。
/**
* -Xms256m
* -Xmx2g
*/
public class ReferenceTest {
public static void main(String[] args) {
WeakReference weakReference = new WeakReference<>(new byte[1024*1024]);
System.out.println(weakReference.get());
// 通知JVM回收资源
System.gc();
System.out.println(weakReference.get());
}
}
JVM配置最大堆内存为2g,而代码中字节数组只占用1m内存,当JVM进行垃圾回收后,其就被回收了。
应用
- ApplicationPackageManager中图标缓存用到
- WeakHashMap
4. Phantom Reference
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
要注意的是,虚引用必须和ReferenceQueue关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
/**
* -Xms256m
* -Xmx2g
*/
public class ReferenceTest {
public static void main(String[] args) {
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference<>(new byte[1024*1024],referenceQueue);
System.out.println(phantomReference.get());
}
}
运行结果输出null
ReferenceQueue
当gc(垃圾回收线程)准备回收一个对象时,如果发现它还仅有软引用(或弱引用,或虚引用)指向它,就会在回收该对象之前,把这个软引用(或弱引用,或虚引用)加入到与之关联的引用队列(ReferenceQueue)中。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。
当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。
Java GC机制
1. 什么时候
GC具有不确定性,具体由JVM的回收机制决定,但内部不足时肯定会被调用,或者应用比较空闲时。我们无法手动指定其进行回收,也可以调用System.gc来提醒垃圾收集器进行收集,大多数会执行,但不保证会执行。主流Java虚拟机的垃圾收集器都采用分带收集算法(Generation Collection)。
- Minor Collection:新生代垃圾收集
- Full Collection:对老年代进行垃圾收集,收集频率较低,耗时长,速度一般会比Minor GC慢10倍以上。
Minor GC触发条件:
当Eden区满时,触发Minor GC。
Full GC触发条件:
- System.gc()方法的调用
此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。
- 老年代空间不足
升到老年代的对象大于老年代剩余空间,会执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
当然可以通过调优,用-XX:NewRatio
控制新生代和老年代的占用空间比列,或者用-XX:MaxTenuringThreshold
控制对象经历多少次Minor GC才晋升到老年代,默认为15。使得对象存储空间延迟达到Full GC,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期
2. 对什么东西
判断对象是否需要回收有两种垃圾标记算法:1. 引用计数算法,2. 根搜索算法
当对象引用计数为0,或者超出了作用域(但不包含超出作用域还被特殊强引用GC Roots持有),对象不可达时会进行标记,当进行GC时,会判断该对象是否重写了finilize方法,如果重写则回调用,但如果调用后还是不可达,那该对象在下次gc时会被回收。
3. 做了什么事情
删除不使用的对象,回收内存空间。对于Minor GC会采用复制清理算法(存活对象复制到To Survivor,回收Eden和from Survivor需要回收的对象,然后将To survivor和From Survivor空间位置互换)。对于Full GC采用的是标记-压缩算法(将回收对象清除后,把存活对象向左移动,使占用内存工整)
java GC是在什么时候,对什么东西,做了什么事情?