Java引用类型与ThreadLocal

Java引用类型

强引用

csharp 复制代码
public class M {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

Object的finalize()方法是对象被回收时被调用的方法。一般来说不要去修改这个方法,执行你的代码可能会导致垃圾回收变慢,进而引发OOM的风险。

java 复制代码
public class NormalReference {
    public static void main(String[] args) throws IOException {
        M m = new M();
        m = null;
        System.gc(); //DisableExplicitGC
        System.out.println(m);

        System.in.read(); // 阻塞main线程,给垃圾回收线程时间执行
    }
}

输出结果:说明gc会在何时的时间回收没有引用的对象

csharp 复制代码
null
finalize

软引用

软引用适合用来做缓存

csharp 复制代码
public class T02_SoftReference {
    // 要做这个实验,需要把设置 -Xmx=20M 在idea中可以设置VM options = -Xmx20M
    public static void main(String[] args) {
        SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);// 1024 * 1024 = 1M

        
        System.out.println(sr.get()); // 拿得到,没有回收
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sr.get()); // 拿得到,没有回收

        // 再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
        byte[] b = new byte[1024 * 1024 * 12];
        System.out.println(sr.get()); // 拿不到,回收了

    }
}

虚引用

虚引用的应用场景:

虚引用的数据永远拿不到,只有在回收的时候,把对象放进ReferenceQueue中。

csharp 复制代码
public class T04_PhantomReference {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();

    public static void main(String[] args) throws InterruptedException {
        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
        System.out.println(phantomReference.get()); // 虚引用的数据永远拿不到

        ByteBuffer b = ByteBuffer.allocateDirect(1024);

        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(phantomReference.get());
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference<? extends M> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ----" + poll.get());
                }
            }
        }).start();

        Thread.sleep(1);

    }
}

直接内存的使用方式:(这里面就包含了虚引用的使用)

arduino 复制代码
public class TestDirectByteBuffer {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
}

弱引用

csharp 复制代码
public class T03_WeakReference {
   public static void main(String[] args) throws InterruptedException {
       WeakReference<M> wr = new WeakReference<>(new M());

       System.out.println(wr.get()); // 能拿到
       System.gc();
       Thread.sleep(1); // 睡1s让gc回收完
       System.out.println(wr.get()); // 拿不到,我们并没有把wr=null,但是System.gc()方法执行后直接就回收了,垃圾回收器看到弱引用就直接当垃圾回收了
   }
}

输出结果:

kotlin 复制代码
com.example.code.gc.M@4d7e1886
finalize
null

垃圾回收器看到弱引用就当垃圾看直接回收。

Q:那有这个跟没有这个引用有什么区别?不都是直接被gc回收吗?

A:经典应用场景就是ThreadLocal。WeakHashMap。

csharp 复制代码
public class ThreadLocal2 {
   static ThreadLocal<Person> tl = new ThreadLocal<>();

   public static void main(String[] args) {
       new Thread(() -> {
           try {
               Thread.sleep(1);
               // 第一个线程设置对象
               tl.set(new Person("zhangsan"));
               System.out.println(tl.get());
//                tl.remove();
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
       }).start();

       new Thread(() -> {
           try {
               Thread.sleep(2);
               // 第二个线程获取对象,拿不到,即使都是操作的同一个对象tl
               System.out.println(tl.get());
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
       }).start();
   }

   static class Person{
       public Person(String name) {
           this.name = name;
       }

       String name = "zhangsan";
   }
}

输出结果:同一个tl对象,都执行get方法,一个能拿到,一个拿不到

kotlin 复制代码
com.example.code.gc.ThreadLocal2$Person@1b9c704e
null

ThreadLocal

跟线程挂钩

拿到的是当前线程池的成员变量

Thread销毁的时候,ThreadLocalMap也被回收了,但是很多Thread是不会被回收的,比如说线程池里面的核心线程。要避免内存泄漏,这里entry的key设置为虚引用,外面的tl被设置为null就能保证key会被回收了。但是还是会有内存泄漏的风险。key为null了,value就访问不到。value是一个强引用。这种情况最好还是要调用tl.remove()方法进行回收。才能保证内存不会泄漏。

相关推荐
带刺的坐椅8 分钟前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看2 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程2 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t2 小时前
ZIP工具类
java·zip
lang201509282 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan3 小时前
第10章 Maven
java·maven
百锦再4 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说4 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多4 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
百锦再4 小时前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven