概述
Java 数据类型分两大类:基本类型和引用类型。
引用类型指向一个对象,不是原始值,指向对象的变量是引用变量。在 Java 里,除了基本类型,其他类型都属于引用类型,它主要包括:类、接口、数组、枚举、注解。
JVM根据数据类型,对程序数据的管理就规范化了,不同的数据类型,它的存储形式和位置是不一样的。
通过引用,可以对堆中的对象进行操作。比如:
Person person = new Person("张三");
这里的变量 person 就是指向Person 实例“”张三”的引用,我们一般都是通过 person 来操作“张三”实例。
在 JDK 1.2 之前,Java 中的引用的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 refrence 数据是代表某块内存、某个对象的引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为力。
比如我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。
在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为
- 强引用(Strong Reference
- 软引用(Soft Reference
- 弱引用(Weak Reference
- 虚引用(Phantom Reference
这四种引用强度依次逐渐减弱。Java 中引入四种引用的目的是让程序自己决定对象的生命周期,JVM 是通过垃圾回收器对这四种引用做不同的处理,来实现对象生命周期的改变。
强引用(StrongReference)
Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。
Object obj=new Object();
当一个对象被强引用变量引用时,是不可能被垃圾回收器回收的,即使该对象永远不会被用到也不会被回收。
当内存不足,JVM 开始垃圾回收,对于强引用的对象,就算是出现了内存泄漏也不会对该对象进行回收,打死都不收。因此强引用有时也是造成 Java 内存泄露的原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集器回收。(具体回收时机还要要看垃圾收集策略)。
public class StrongRefenenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = o1;
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(o2);
}
}
输出:
null
java.lang.Object@4b1210ee
尽管 o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收。
软引用(SoftReference)
软引用需要用java.lang.ref.SoftReference类来实现,用来描述一些还有用,但并非必需的对象。当内存充足时,垃圾回收器不会清理具有软引用的对象,只有当内存不足时垃圾回收器才会去清理这些对象,如果清理完软引用的对象后内存还是不足才会抛出异常。
public class SoftRefenenceDemo {
public static void main(String[] args) throws InterruptedException {
softRefMemory();
}
/** * JVM配置 -Xmx23m` ,然后故意new一个一个大对象,使内存不足产生 OOM,看软引用回收情况 */
private static void softRefMemory() throws InterruptedException {
byte[] b1 = new byte[1024*1024*10]; //10m
SoftReference<byte[]> softReference = new SoftReference<byte[]>(b1);
System.out.println(softReference.get());
b1 = null;
System.gc();//启动GC
Thread.sleep(500);
System.out.println(softReference.get());
//再创建一个数组,堆中存不下的时候,垃圾回收器工作
//先回收一次,如果第一次回收后内存还是不够
//则再清理第二次,这一次会把软引用对象清除
byte[] b2 = new byte[1024*1024*15]; //15m
System.out.println(softReference.get());//null
}
}
先设置启动参数最大堆内存 -Xmx100M
[B@4b1210ee
[B@4b1210ee
[B@4b1210ee
结果看出,当内存充足时,垃圾回收器不会清理具有软引用的对象。
设置启动参数最大堆内存 -Xmx23M
[B@4b1210ee
[B@4b1210ee
null
结果看出,只有当内存不足时,垃圾回收器才会去清理软引用对象。
设置启动参数最大堆内存 -Xmx10M,报错:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at 四大引用.SoftRefenenceDemo.softRefMemory(SoftRefenenceDemo.java:13)
at 四大引用.SoftRefenenceDemo.main(SoftRefenenceDemo.java:8)
结果看出,如果清理完软引用的对象后内存还是不足,才会抛出异常。
JAVA中软引用也是一个类,实例化软引用类对象,需要关联其他的对象。比如上面案例中,变量softReference 的引用指向的是new SoftReference()这个实例对象,属于强引用关系,而在这个实例对象的里面又去引用了我们new出来的byte数组实例,这个引用是软引用关系。
软引用非常适合用在缓存中,假如用户访问的系统中需要加载很多图片,内存够用的时候可以缓存很多图片,假如内存不够用了,再把图片先回收掉也无妨,下次需要的时候再加载一次即可。
弱引用(WeakReference)
弱引用需要用java.lang.ref.WeakReference类来实现,用来描述非必需对象。无论内存够不够,只要垃圾回收器启动,弱引用关联的对象肯定被回收。
public class WeakReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
WeakReference<Object> w1 = new WeakReference<Object>(o1);
System.out.println(o1);
System.out.println(w1.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(w1.get());
myHashMap();
myWeakHashMap();
}
public static void myHashMap() {
HashMap<String, String> map = new HashMap<String, String>();
String key = new String("k1");
String value = "v1";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
public static void myWeakHashMap() throws InterruptedException {
WeakHashMap<String, String> map = new WeakHashMap<String, String>();
//String key = "weak";
// 刚开始写成了上边的代码
// 思考一下,写成上边那样会怎么样? 那可不是引用了
String key = new String("weak");
String value = "map";
map.put(key, value);
System.out.println(map);
//去掉强引用
key = null;
System.gc();
Thread.sleep(1000);
System.out.println(map);
}
}
输出:
java.lang.Object@4b1210ee
java.lang.Object@4b1210ee
null
null
{k1=v1}
{k1=v1}
{weak=map}
{}
只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。
弱引用可以用来解决内存泄露的问题。ThreadLocal中的key就使用到了弱引用来防止内存泄露。
注:不管是软引用还是弱引用,实例化引用类对象,需要关联其他的对象。比如,
Object o1 = new Object();
WeakReference<Object> w1 = new WeakReference<Object>(o1);
o1必须置为null(没有任何强引用到new Object()对象时),引用类对象w1 才会被垃圾回收器处理。如果o1不为null,引用类对象也不会被加入垃圾回收。
虚引用(PhantomReference)
如果一个对象具有虚引用,那么它和没有任何引用一样,被虚引用关联的对象引用通过get方法获取到的永远为null,也就是说这种对象在任何时候都有可能被垃圾回收器回收,通过这种方式关联的对象也无法调用对象中的方法。
虚引用主要是用来管理堆外内存的,通过ReferenceQueue这个类实现,当一个对象被回收的时候,会向这个引用队列里面添加相关数据,给一个通知。
设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。
参考文档:
https://zhuanlan.zhihu.com/p/160023776
https://zhuanlan.zhihu.com/p/147343299?from_voters_page=true&ivk_sa=1024320u