zoukankan      html  css  js  c++  java
  • Java 强、弱、软、虚,你属于哪一种?

    Java中的四种引用

    Java中有四种引用类型:强引用、软引用、弱引用、虚引用。

    Java为什么要设计这四种引用

    Java的内存分配和内存回收,都不需要程序员负责,都是由伟大的JVM去负责,一个对象是否可以被回收,主要看是否有引用指向此对象,说的专业点,叫可达性分析。

    Java设计这四种引用的主要目的有两个:

    1. 可以让程序员通过代码的方式来决定某个对象的生命周期;

    2. 有利用垃圾回收。

    强引用

    强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用:

    Object o = new Object();

    这种就是强引用了,是不是在代码中随处可见,最亲切。 

    只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。

    那么什么时候才可以被回收呢?当强引用和对象之间的关联被中断了,就可以被回收了。

    我们可以手动把关联给中断了,方法也特别简单:

    o = null;

    我们可以手动调用GC,看看如果强引用和对象之间的关联被中断了,资源会不会被回收,为了更方便、更清楚的观察到回收的情况,我们需要新写一个类,然后重写finalize方法,下面我们来进行这个实验:

    public class Student {    @Override
        protected void finalize() throws Throwable {
            System.out.println("Student 被回收了");
        }
    }
    
    public static void main(String[] args) {
        Student student = new Student();
        student = null;
        System.gc();
    }

    运行结果:

    Student 被回收了

    可以很清楚的看到资源被回收了。

    当然,在实际开发中,千万不要重写finalize方法

    在实际的开发中,看到有一些对象被手动赋值为NULL,很大可能就是为了“特意提醒”JVM这块资源可以进行垃圾回收了。点击这里获取一份 JVM 实战教程。

    软引用

    下面先来看看如何创建一个软引用:

    SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());

    软引用就是把对象用SoftReference包裹一下,当我们需要从软引用对象获得包裹的对象,只要get一下就可以了:

    SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());
    Student student = studentSoftReference.get();
    System.out.println(student);

    软引用有什么特点呢: 

    当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有在内存不足,JVM才会回收该对象。

    还是一样的,必须做实验,才能加深印象:

    SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
    System.out.println(softReference.get());
    System.gc();
    System.out.println(softReference.get());        
    byte[] bytes = new byte[1024 * 1024 * 10];
    System.out.println(softReference.get());

    我定义了一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10Mbyte[]。

    运行程序,需要带上一个参数:

    -Xmx20M

    代表最大堆内存是20M。

    运行结果:

    [B@11d7fff
    [B@11d7fff
    null

    可以很清楚的看到手动完成GC后,软引用对象包裹的byte[]还活的好好的,但是当我们创建了一个10M的byte[]后,最大堆内存不够了,所以把软引用对象包裹的byte[]给干掉了,如果不干掉,就会抛出OOM。

    软引用到底有什么用呢?比较适合用作缓存,当内存足够,可以正常的拿到缓存,当内存不够,就会先干掉缓存,不至于马上抛出OOM。说到缓存,大家可以关注微信公众号Java技术栈获取更多干货。

    弱引用

    弱引用的使用和软引用类似,只是关键字变成了WeakReference

    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
    System.out.println(weakReference.get());

    弱引用的特点是不管内存是否足够,只要发生GC,都会被回收:

    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
    System.out.println(weakReference.get());
    System.gc();
    System.out.println(weakReference.get());

    运行结果:

    [B@11d7fffnull

    可以很清楚的看到明明内存还很充足,但是触发了GC,资源还是被回收了。 
    弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。

    虚引用

    虚引用又被称为幻影引用,我们来看看它的使用

    ReferenceQueue queue = new ReferenceQueue();
    PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
    System.out.println(reference.get());

    虚引用的使用和上面说的软引用、弱引用的区别还是挺大的,我们先不管ReferenceQueue 是个什么鬼,直接来运行:

    null

    竟然打印出了null,我们来看看get方法的源码:

    public T get() {        
      return null;
    }

    这是几个意思,竟然直接返回了null。

    这就是虚引用特点之一了:无法通过虚引用来获取对一个对象的真实引用。

    那虚引用存在的意义是什么呢?这就要回到我们上面的代码了,我们把代码复制下,以免大家再次往上翻:

    ReferenceQueue queue = new ReferenceQueue();
    PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
    System.out.println(reference.get());

    创建虚引用对象,我们除了把包裹的对象传了进去,还传了一个ReferenceQueue,从名字就可以看出它是一个队列。

    虚引用的特点之二就是 虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。

    我们来用代码实践下吧

    ReferenceQueue queue=new ReferenceQueue();
            List<byte[]> list=new ArrayList<byte[]>();
            PhantomReference<Student> reference=new PhantomReference<Person>(new Student(),queue);
            new Thread() {
                public void run() {
                    for(int i=0;i<100;i++) {
                        list.add(new byte[1024*1024]);
                    }
                    
                };
            }.start();
            
            new Thread(()-> {
                while(true) {
                    Reference po=queue.poll();
                    if(po!=null) {
                        System.out.println("虚引用被回收了"+po);
                    }
                    
                }
            }).start();
            Scanner sc=new Scanner(System.in);
            sc.hasNext();
        }

    运行结果:

    Student 被回收了
    虚引用被回收了:java.lang.ref.PhantomReference@1ade6f1

    我们简单的分析下代码: 
    第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。 
    第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。

    从运行结果可以看到:当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。

    虚引用有什么用呢?在NIO中,就运用了虚引用管理堆外内存。

     

  • 相关阅读:
    Ajax基础:3.Json
    Head First Design Patterns State Pattern
    Head First Design Patterns Template Method Pattern
    Articles For CSS Related
    Head First Design Patterns Decorator Pattern
    代码审查工具
    How To Be More Active In A Group
    Head First Design Patterns Factory Method Pattern
    Head First Design Patterns Composite Pattern
    Tech Articles
  • 原文地址:https://www.cnblogs.com/zouhong/p/13454587.html
Copyright © 2011-2022 走看看