java中int和integer的区别_Java中关于强、软、弱、虚引用的区别
前言
在JDK 1.2版本之前,如果一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,对象的引用被分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。 JVM(Java Virtual Machine)是Java中提供的Java虚拟机,JVM负责内存的分配和回收,这是它的优点,不用像使用C语言,需要手动的释放内存。但同时JVM也是有它的缺点,就是不够灵活,为解决对内存操作不够灵活这个问题,可以采用软引用等方法。具体描述
1. 强引用
强引用是最普遍的引用,如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,让程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 强引用就是像Person p = new Person(); 这种p就属于是一个强引用类型,如果p这个引用始终指向了new Person()这块内存的话,JVM则不会对这块内存进行回收。示例
public class M { @Override protected void finalize() throws Throwable { System.out.println("finalize"); }}public class T01_NormalReference { public static void main(String[] args) { M m = new M(); // m = null; System.gc(); try { System.in.read(); } catch (IOException e) { e.printStackTrace(); } }}
上例中,定义了个类重写了finalize()方法(默认情况下,当JVM准备释放掉一块内存之前,会先调用finalize()方法),所以当JVM准备回收内存时,就会执行我们重写的方法打印"finalize",下面定义了一个类,初始化了一个M类的对象,显式的调用一下gc()方法,让JVM准备回收内存。
如果根据上述强引用的概念,由于M m = new M();m的强引用并没有为空,它依然指向了new M()的这块内存区域,所以最后的执行结果并没有打印出"finalize"(没有调用finalize()方法),如果我们将m的引用设置为null,这时控制台就会打印出"finalize"字符串,因为强引用m不再指向实际的内存地址了,意为new M()没有强引用再指向它,所以JVM将内存回收。
最后的打印结果如下:
2. 软引用(SoftReference)
软引用的概念是如果当前内存空间足够,垃圾回收器就不会回收它,如果当前内存不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以一直被程序使用。软引用可以用来实现高速缓存。示例
public class T02_SoftReference { public static void main(String[] args) { SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]); System.out.println(m.get()); System.gc(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(m.get()); byte[] b = new byte[1024*1024*12]; System.out.println(m.get()); } }
上例中,初始化了一个SoftReference软引用对象 m,并在其中设置了一个10M大小的byte[ ]数组,然后调用get()方法,判断该对象的内存是否被回收,如果被回收了调用get()方法就会得到null,接着调用gc()垃圾回收器,等待一会儿后再去调用get()方法,后面再初始化一个大小为12M的字节数组,再次调用get()方法,这个程序最后的执行结果如下:
程序中调用的3次get()都会得到结果,这是因为没有设置当前程序的最大内存空间,假设我们把最大内存设置成为20M的话,看下图:
最终的执行结果会是怎样的:
可以看到,前两次调用get()方法都成功打印了,当调用第三次get()方法时,打印为空。这是因为当前环境的内存空间只有20M,开始软引用对象中创建的数组大小为10M,当第二次调用时,因为当前内存足够,所以不会回收对象内存,但下面又创建了一个大小为12M的数组,这时内存不够用了,JVM就会将之前的内存回收,所以最后调用m.get()方法得到的是null,因为m引用指向的对象所在的内存已经被回收了。3. 弱引用(WeakReference)
弱引用的概念是只有弱引用的对象会拥有更短暂的生命周期。在垃圾回收器线程扫描它的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会去回收它的内存。示例
public class T03_WeakReference { public static void main(String[] args) { WeakReference m = new WeakReference<>(new M()); System.out.println(m.get()); System.gc(); System.out.println(m.get()); }}
上例中,实例化了一个WeakReference弱引用对象 m,为其设置了一个M对象,分别打印两次get()方法,在中间调用gc线程回收内存,执行结果如下:
根据弱引用的概念我们可知,为什么第二次打印get()方法得到的值为null,这是因为垃圾回收器只要扫描到弱引用指向的对象,就会将该对象的内存空间回收,不管当前的内存空间是否充足。
4. 虚引用
虚引用和与其他的引用不大相同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用的作用主要是用来跟踪对象被垃圾回收的活动。 虚引用与软引用弱引用的区别就在于:虚引用必须要和ReferenceQueue引用队列联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到它所关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否要被垃圾回收了。示例
public class T04_PhantomReference { private static final List LIST = new LinkedList<>(); private static final ReferenceQueue QUEUE = new ReferenceQueue<>(); public static void main(String[] args) { PhantomReference phantomReference = new PhantomReference<>(new M(), QUEUE); new Thread(() -> { while (true) { LIST.add(new byte[1024 * 1024]); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } System.out.println(phantomReference.get()); } }).start(); new Thread(() -> { while (true) { Reference extends M> poll = QUEUE.poll(); if (poll != null) { System.out.println("--- 虚引用对象被Jvm回收了 ---" + poll); } } }).start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }}// 执行结果nullfinalizenullnullnullnullnullnullnullnullnullnull--- 虚引用对象被Jvm回收了 ---java.lang.ref.PhantomReference@2831008dnullnullnullnullnullnullException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at c_v5.T04_PhantomReference.lambda$main$0(T04_PhantomReference.java:20) at c_v5.T04_PhantomReference$$Lambda$1/1078694789.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)
上例中,首先创建了一个List对象,创建了一个ReferenceQueue引用队列,实例化一个PhantomReference虚引用对象,里面放入一个对象,和一个ReferenceQueue队列,然后创建两个线程,线程1不断的往引用对象中放一个对象,线程2持续的对队列Poll,如果Poll不为空的时候,说明虚引用已经被jvm回收了。
最后的结果是,当每一次调用get()方法时,获取虚引用对象时发现所有的都是null,因为我们是没办法通过虚引用get出它所指向的对象的,程序直到内存到了我们指定的大小后,抛出OutOfMemoryError错误。
虚引用主要用在什么地方?先来看一个图,如下:
首先看左边的这个DirectByteBuffer,它是NIO中提供的一种Buffer类型,叫做直接内存,直接内存它是不会被JVM虚拟机管理的,由操作系统管理(又称堆外内存),DirectByteBuffer是可以指向堆外内存的。
试想一下,如果DirectByteBuffer指向为null,那么堆外内存要怎么样回收呢?这种情况可以使用虚引用,当检测到ReferenceQueue队列中的虚引用被垃圾回收器回收时,就可以去堆外内存进去内存的回收了。
总结
在实际的程序设计中一般很少使用弱引用和虚引用,软引用的使用情况较多,因为软引用可以加速JVM对垃圾内存的回收速度,维护系统的运行安全,防止内存泄漏等问题。