• Java中的引用类型和使用场景


    作者:Grey

    原文地址:Java中的引用类型和使用场景

    Java中的引用类型有哪几种?

    Java中的引用类型分成强引用软引用, 弱引用, 虚引用

    强引用

    没有引用指向这个对象,垃圾回收会回收

    package git.snippets.juc;
    
    import java.io.IOException;
    
    public class NormalRef {
        public static void main(String[] args) throws IOException {
            M m = new M();
            m = null;
            System.gc();
            System.in.read();
        }
        static class M {
            M() {}
            @Override
            protected void finalize() throws Throwable {
                System.out.println("finalized");
            }
        }
    }
    

    软引用

    当有一个对象被一个软引用所指向的时候,只有系统内存不够用的时候,才会被回收,可以用做缓存(比如缓存大图片)

    示例如下代码:注:执行以下方法的时候,需要把VM options设置为-Xms20M -Xmx20M

    package git.snippets.juc;
    
    import java.io.IOException;
    import java.lang.ref.SoftReference;
    import java.util.concurrent.TimeUnit;
    
    /**
     * heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
     * 软引用,适合做缓存
     * 示例需要把Vm options设置为:-Xms20M -Xmx20M
     */
    public class SoftRef {
        public static void main(String[] args) throws IOException {
            SoftReference<byte[]> reference = new SoftReference<>(new byte[1024 * 1024 * 10]);
            System.out.println(reference.get());
            System.gc();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(reference.get());
            byte[] bytes = new byte[1024 * 1024 * 10];
            System.out.println(reference.get());
            System.in.read();
        }
    }
    

    上述代码在第一次执行System.out.println(reference.get())时候,由于堆的最大最小值都是20M,而我们分配的byte数组是10M,没有超过最大堆内存,所以执行垃圾回收,软引用不被回收,后续又调用了byte[] bytes = new byte[1024 * 1024 * 10];再次分配了10M内存,此时堆内存已经超过设置的最大值,会进行回收,所以最后一步的System.out.println(reference.get());无法get到数据。

    弱引用

    只要垃圾回收,就会回收。如果有一个强引用指向弱引用中的这个对象,如果这个强引用消失,这个对象就应该被回收。一般用在容器里面。

    代码示例如下:

    package git.snippets.juc;
    
    import java.lang.ref.WeakReference;
    import java.util.HashMap;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 弱引用遭到gc就会回收
     * ThreadLocal应用,缓存应用,WeakHashMap
     */
    public class WeakRef {
        public static void main(String[] args) {
            WeakReference<T> reference = new WeakReference<>(new T());
            System.out.println(reference.get());
            System.gc();
            System.out.println(reference.get());
        }
        static class T {
            T() {}
            @Override
            protected void finalize() {
                System.out.println("finalized");
            }
        }
    }
    

    如果执行了一次GCreference.get() 获取到的值即为空。

    弱引用的使用场景

    弱引用的一个典型应用场景就是ThreadLocal,以下是ThreadLocal的的简要介绍

    set方法

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    get方法

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    ThreadLocalMap是当前线程的一个成员变量,所以,其他线程无法读取当前线程设置的ThreadLocal值。

    ThreadLocal.ThreadLocalMap threadLocals = null;
    

    ThreadLocal的主要应用场景

    场景一:每个线程需要一个独享的对象:假设有100个线程都需要用到SimpleDateFormat类来处理日期格式,如果共用一个SimpleDateFormat,就会出现线程安全问题,导致数据出错,如果加锁,就会降低性能,此时使用ThreadLocal,给每个线程保存一份自己的本地SimpleDateFormat,就可以同时保证线程安全和性能需求。

    场景二:每个线程内部保存全局变量,避免传参麻烦:假设一个线程的作用是拿到前端用户信息,逐层执行Service1Service2Service3Service4层的业务逻辑,其中每个业务层都会用到用户信息,此时一个解决办法就是将User信息对象作为参数层层传递,但是这样会导致代码冗余且不利于维护。此时可以将User信息对象放入当前线程的Threadlocal中,就变成了全局变量,在每一层业务层中,需要使用的时候直接从Threadlocal中获取即可。

    场景三:Spring的声明式事务,数据库连接写在配置文件,多个方法可以支持一个完整的事务,保证多个方法是用的同一个数据库连接(其实就是放在ThreadLocal里面)

    了解了ThreadLocal简要介绍以后,我们可以深入理解一下ThreadLocal的一个内部原理,前面提到,ThreadLocalset方法实际上是往当前线程的一个threadLocals表中插入一条记录,而这个表中的记录都存在一个Entry对象中,这个对象有一个key和一个value,key就是当前线程的ThreadLocal对象。

    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    这个Entry对象继承了WeakReference, 且构造函数调用了super(k), 所以Entry中的key是通过一个弱引用指向的ThreadLocal,所以,我们在主方法中调用

    ThreadLocal<Object> tl = new ThreadLocal<>();
    

    tl是通过强引用指向这个ThreadLocal对象。

    当前线程的threadLocalMap中的key是通过弱引用指向ThreadLocal对象,这样就可以保证,在tl指向空以后,这个ThreadLocal会被回收,否则,如果threadLocalMap中的key是强引用指向ThreadLocal对象话,这个ThreadLocal对象永远不会被回收。就会导致内存泄漏。

    但是,即便key用弱引用指向ThreadLocal对象,key值被回收后,Entry中的value值就无法被访问到了,且value是通过强引用关联,所以,也会导致内存泄漏,所以,每次在ThreadLocal中的对象不用了,记得要调用remove方法,把对应的value也给清掉。

    虚引用

    用于管理堆外内存回收

    虚引用关联了一个对象,以及一个队列,只要垃圾回收,虚引用就被回收,一旦虚引用被回收,虚引用会被装到这个队列,并会收到一个通知(如果有值入队列,会得到一个通知)所以,如果想知道虚引用何时被回收,就只需要不断监控这个队列是否有元素加入进来了。

    虚引用里面关联的对象用get方法是无法获取的。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.LinkedList;
    import java.util.List;
    
    // 配置 -Xms20M -Xmx20M
    public class PhantomRef {
        private static final List<Object> LIST = new LinkedList<>();
        private static final ReferenceQueue<P> QUEUE = new ReferenceQueue<>();
    
    
        public static void main(String[] args) {
            PhantomReference<P> phantomReference = new PhantomReference<>(new P(), 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 P> poll = QUEUE.poll();
                    if (poll != null) {
                        System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                    }
                }
            }).start();
    
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    
        static class P {
            @Override
            protected void finalize() throws Throwable {
                System.out.println("finalized");
            }
        }
    }
    

    虚引用的应用场景

    JDK的NIO包中有一个DirectByteBuffer, 这个buffer指向的是堆外内存,所以当这个buffer设置为空的时候,Java的垃圾回收无法回收,所以,可以用虚引用来管理这个buffer,当我们检测到这个虚引用被垃圾回收器回收的时候,可以做出相应的处理,去回收堆外内存。

    源码

    juc

  • 相关阅读:
    安装gmsll
    常用LInux命令和操作
    yum 一键安装 jdk
    Linux目录详解,软件应该安装到哪个目录
    安装npm
    linux安装mysql (rpm + yum)
    springboot 打包jar 运行找资源文件
    rpm包安装java jar开机自启
    centos7设置服务开机自启
    linux(centos7) nginx 配置
  • 原文地址:https://www.cnblogs.com/greyzeng/p/15377284.html
Copyright © 2020-2023  润新知