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

相关推荐
小魏冬琅12 分钟前
探索面向对象的高级特性与设计模式(2/5)
java·开发语言
TT哇26 分钟前
【Java】数组的定义与使用
java·开发语言·笔记
look_outs44 分钟前
JavaSE笔记2】面向对象
java·开发语言
武子康1 小时前
大数据-191 Elasticsearch - ES 集群模式 配置启动 规划调优
java·大数据·elk·elasticsearch·搜索引擎·全文检索
A_aspectJ1 小时前
‌Spring MVC的主要组件有哪些?
java·spring·mvc
塔塔开!.1 小时前
Maven的依赖
java·maven
liuyang-neu1 小时前
力扣第420周赛 中等 3324. 出现在屏幕上的字符串序列
java·算法·leetcode
划]破1 小时前
Maven的安装及配置
java·maven
讓丄帝愛伱1 小时前
dependencyManagement保持maven的多模块依赖版本一致
java·maven
Ellie陈1 小时前
Java已死,大模型才是未来?
java·开发语言·前端·后端·python