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()方法进行回收。才能保证内存不会泄漏。

相关推荐
咕白m6254 分钟前
基于Java 实现 PPT 到 PDF 的高效转换
java
七夜zippoe7 分钟前
Java并发编程基石:深入理解JMM(Java内存模型)与Happens-Before规则
java·开发语言·spring·jmm·happens-before
YDS82925 分钟前
苍穹外卖 —— Spring Task和WebSocket的运用以及订单统一处理、订单的提醒和催单功能的实现
java·spring boot·后端·websocket·spring
速易达网络28 分钟前
C语言常见推理题
java·c语言·算法
m0_6398171532 分钟前
基于springboot纺织品企业财务管理系统【带源码和文档】
java·服务器·前端
q***318334 分钟前
Spring Boot(快速上手)
java·spring boot·后端
q***098039 分钟前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
羊锦磊1 小时前
[ 项目开发 1.0 ] 新闻网站的开发流程和注意事项
java·数据库·spring boot·redis·spring·oracle·json
吴名氏.2 小时前
电子书《21天学通Java(第5版)》
java·开发语言·21天学通java
曼巴UE52 小时前
UE5 C++ JSON 最简单,麻烦的方式,直接读存(一)
java·服务器·前端