Java的引用

news2024/12/25 8:55:21

一、概述

其实java有4种引用,4种可分为强、软、弱、虚。我们将从这四个方面入手进行介绍。

二、强引用

首先看到我们有一个类叫M,在这个类里我重写了一个方法叫finalize(),我们可以看到这个方法是已经被废弃的方法,为什么要重写他呢?主要想说明一下在垃圾回收的过程中,各种引用它不同的表现,垃圾回收的时候,它是会调用finalize()这个方法的,什么意思?当我们new出来一个象,在java语言里是不需要手动回收的,C和C++是需要的,在这种情况下,java的垃圾回收机制会自动的帮你回收这个对象,但是它回收对象的时候它会调用finalize()这个方法,我们重写这个方法之后我们能观察出来,它什么时候被垃圾回收了,什么时候被调用了。

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

我们来解释一下普通的引用NormalReference,普通的引用也就是默认的引用,默认的引用就是说,只要有一个应用指向这个对象,那么垃圾回收器一定不会回收它,这就是普通的引用,也就是强引用,为什么不会回收?因为有引用指向,所以不会回收,只有没有引用指向的时候才会回收,指向谁?指向你创建的那个对象。

我们来看下面这个小程序,我new了一个m出来,然后调用了System.gc(),显式的来调用一下垃圾回收,让垃圾回收尝试一下,看能不能回收这个m,需要注意的是,要在最后阻塞住当前线程,为什么?因为System.gc()是跑在别的线程里边的,如果main线程直接退出了,那整个程序就退出了,那gc不gc就没有什么意义了,所以你要阻塞当前线程,在这里调用了System.in.read()阻塞方法,它没有什么含义,只是阻塞当前线程的意思。

阻塞当前线程就是让当前整个程序不会停止,程序运行起来你会发现,程序永远不会输出,为什么呢?我们想一下,这个M是有一个小引用m指向它的,那有引用指向它,它肯定不是垃圾,不是垃圾的话一定不会被回收。

public class T01_NormalReference {
    public static void main(String[] args) throws IOException {
        M m = new M();
        System.gc(); //DisableExplicitGC
        System.in.read();
    }
}

那你想让它显示回收,怎么做呢?我们让m=null,m=nul的意思就是不会再有引用指向这个M对象了,也就是说把m和new M()之间的引用给打断了,不再有关联了,这个时候再运行程序,你会发现,输出了:finalize,说明什么?说明M对象被回收了,综上所述这个就是强引用

public class T01_NormalReference {
    public static void main(String[] args) throws IOException {
        M m = new M();
        m = null;
        System.gc(); //DisableExplicitGC
        System.in.read();
    }
}

三、软引用

我们来说一下软引用的含义,当有一个对象(字节数组)被一个软引用所指向的时候,只有系统内存不够用的时候,才会回收它(字节数组)

我们来跑一下这个程序,在程序运行的时候,我们来设置一下堆内存最大为20MB,就是说我堆内存直接给你分配20MB,你要设置一下堆内存,如果不设置它永远不会回收的,这个时候我们运行程序你会发现,第三次调用m.get()输出的时候,输出的值为null,我们来分析一下,第一次我们的堆内存这个时候最多只能放20MB,第一次创建字节数组的时候分配了10MB,这个时候堆内存是能分配下的,这个时候我调用了gc来做回收是无法回收的,因为堆内存够用,第二次创建字节数组的时候分配了15MB,这个时候对内存的内存还够15MB吗?肯定是不够的,不够了怎么办?清理,清理的时候既然内存不够用,就会把你这个软引用给干掉,然后15MB内存分配进去,所以这个时候你再去get第一个字节数组的时候它是一个null值,这是就是软引用的含义,用大腿想一想这个软引用的使用场景:做缓存用,这个东西主要做缓存用

//软引用非常适合缓存使用
public class T02_SoftReference {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
        //m = null;
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(m.get());
        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会		 //把软引用干掉
        byte[] b = new byte[1024*1024*15];
        System.out.println(m.get());
    }
}

四、弱引用

接下来我们来说一下弱引用,弱引用的意思是,只要遭遇到gc就会回收,刚才我们说到软引用的概念是,垃圾回收不一定回收它,只有空间不够了才会回收它,所以软引用的生存周期还是比较长的,我们接着说弱应用,弱引用就是说,只要垃圾回收看到这个引用是一个特别弱的引用指向的时候,就直接把它给干掉

我们来看这个小程序,WeakReference<M> m = new WeakReference<>(new M()),这里我们new了一个对象这是第一点,这m指向的是一个弱引用,这个弱引用里边有一个引用,是弱弱的指向了new出来的另外一个M对象,然后通过m.get()来打印这个M对象,接下来gc调用垃圾回收,如果他它没有被回收,你接下来get还能拿到,反之则不能

public class T03_WeakReference {
    public static void main(String[] args) {
        WeakReference<M> m = new WeakReference<>(new M());
        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());
        ThreadLocal<M> tl = new ThreadLocal<>();
        tl.set(new M());
        tl.remove();
    }
}

 

 

我们的想法是这么个想法,但是它里面到底执行了一个什么样的操作呢?我们来看上面的图,从左往右看,首先我们来说当前肯定是有一个线程的,任何一个方法肯定是要运行在某个线程里的,这个线程是我的主线程,在这个线程里有一个线程的局部变量叫tl,tl它new出来了一个ThreadLoal对象,这是一个强引用没问题,然后我又往ThreadLocal里放了一个对象,可是你们是不是还记得,往ThreadLocal里放对象的话,实际上是放到了当前线程的一个threadLocals变量里面,这个threadLocals变量指向的是一个Map,也就是我们把这个M对象给放到了这Map里面,它的key是我们的ThreadLocal对象,value是我们的M对象,我们来回想一下,往ThreadLocal里面set的时候,先拿到当前线程,然后拿到当前线程里面的那个Map,然后通过这个Map把ThreadLocal对象给set进去,这个map.set(this, value)方法中的this是谁?是ThreadLocal对象,set进去的时候往里面放了这么一个东西叫Entry,这个Entry又是什么呢?注意看代码,这个Entry是从弱引用WeakReference继承出来的

现在也就是说有一个Entry,它的父类是一个WeakReference,这个WeakReference里面装的是什么?是ThreadLocal对象,也就是说这个Entry一个key一个value,而这个Entry的key的类型是ThreadLocal,这个value当然就是我们的那个M的值或者其它什么值这个不重要,这个key是ThreadLocal,而由于这个Entry是从ThreadLocal继承的,在Entry构造的时候调用了super(k),这个k指的就是ThreadLocal对象,我们想一下WeakReference不就相当于new WeakReference key吗?

//ThreadLocal源码
public class ThreadLocal<T> {
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t){
        return t.threadLocals;
    }
    private void set(ThreadLocal<?> key, Object value) {
       Entry[] tab = table;
       int len = tab.length;
       int i = key.threadLocalHashCode & (len-1);
       for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
           ThreadLocal<?> k = e.ge
           if (k == key) {
               e.value = value;
               return;
        
           if (k == null) {
               replaceStaleEntry(key, value, i);
               return;
           }
       tab[i] = new Entry(key, value);
       int sz = ++size;
       if (!cleanSomeSlots(i, sz) && sz >= threshold)
           rehash();
   }
   static class Entry extends WeakReference<ThreadLocal<?>> {
       /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
   }
}

我们来看下面图中从左开始看,这时候我们应该明白了,这里tl是一个强引用指向这个ThreadLocal对象,而Map里的key是通过一个弱引用指向了一个ThreadLocal对象,我们假设这是个强引用,当tl指向这个ThreadLocal对象消失的时候,tl这个东西是个局部变量,方法已结束它就消失了,当tl消失了,如果这个ThreadLocal对象还被一个强引用的key指向的时候,这个ThreadLocal对象能被回收吗?肯定不行,而且由于这个线程有很多线程是长期存在的,比如这个是一个服务器线程,7*24小时一年365天不间断运行,那么不间断运行的时候,这个tl会长期存在,这个Map会长期存在,这个Map的key也会长期存在,这个key长期存在的话,这个ThreadLocal对象永远不会被消失,所以这里是不是就会有内存泄漏,但是如果这个key是弱引用的话还会存在这个问题吗?当这个强引用消失的时候这个弱引用是不是自动就会回收了,这也是为什么用WeakReference的原因

 关于ThreadLocal还有一个问题,当我们tl这个强引用消失了,key的指向也被回收了,可是很不幸的是这个key指向了一个null值,但是这个threadLocals的Map是永远存在的,相当于说key/value对,你这个key是null的,你这个value指向的东西,你的这个10MB的字节码,你还能访问到吗?访问不到了,如果这个Map越积攒越多,越来越多,它还是会内存泄漏,怎么办呢?所以必须记住这一点,使用ThreadLocal里面的对象不用了,务必要remove掉,不然还会有内存泄漏。

五、虚引用

对于虚引用它就干一件事,它就是管理堆外内存的,首先第一点,这个虚引用的构造方法至少都是两个参数的,第二个参数还必须是一个队列,这个虚引用基本没用,就是说不是给你用的,那么它是给谁用的呢?是给写JVM(虚拟机)的人用的

我们来看下面的小程序,在小程序里创建了一个List集合用于模拟内存溢出,还创建了一个ReferenceQueue(引用队列),在main方法里创建一个虚引用对象PhantomReference,这个虚引用对象指向的这个内存里是什么样子的呢?有一个phantomReference对象指向了一个new出来的PhantomReference对象,这个对像里面可以访问两个内容,第一个内容是它又通过一个特别虚的引用指向了我们new出来的一个M对象,第二个内容它关联了一个Queue(队列),这个时候一但虚引用被回收,这个虚引用会装到这个队列里,也就是说这个队列是干什么的呢?就是垃圾回收的时候,一但把这个虚引用给回收的时候,会装到这个队列里,让你接收到一个通知,什么时候你检测到这个队列里面如果有一个引用存在了,那说明什么呢?说明这个虚引用被回收了,这个虚引用叫特别虚的引用,指向的任何一个对象,垃圾回收二话不说,上来就把这个M对象给干掉这是肯定的,只要有垃圾回收, 而且虚引用最关键的是当M对象被干掉的时候,你会收到一个通知,通知你的方式是什么呢?通知你的方式就是往这个Queue(队列)里放进一个值

那么我们这个小程序是什么意思呢?在小程序启动前先设置好了堆内存的最大值,然后看第一个线程启动以后,它会不停的往List集合里分配对象,什么时候内存占满了,触发垃圾回收的时候,另外一个线程就不断的监测这个队列里边的变动,如果有就说明这个虚引用被放进去了,就说明被回收了

在第一个线程启动后我们会看到,无论我们怎么get这个phantomReference里面的值,它输出的都是空值,虚引用和弱引用的区别就在于,弱引用里边有值你get的时候还是get的到的,但是虚引用你get里边的值你是get不到的

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) {
        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), 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 M> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                }
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printSackTrace();
        }
    }
}

那么我们想一下你拿不到这里边的值我用它来干什么呢?这里再强调一遍,只是为了给你一个通知,通知的时候放到队列里,这虚引用干什么用?就是写JVM的人拿来用,写JVM的人用的时候怎么用呢?他会当Queue这个值,检测到队列里边有虚引用指向这个东西被回收的时候做出相应的处理,什么时候出现相应的处理呢?

经常会有一种情况,NIO里边有一个比较新的新的Buffer叫DirectByteBuffer(直接内存),直接内存是不被JVM(虚拟机)直接管理的内存,被谁管理?被操作系统管理,又叫做堆外内存,这个DirectByteBuffer是可以指向堆外内存的,那我们想一下,如果这个DirectByteBuffer设为null,垃圾回收器能回收DirectByteBuffer吗?它指向内存都没在堆里,你怎么回收它,所以没有办法回收,那么写虚拟机的人怎么回收DirectByteBuffer呢?如果有一天你也用到堆外内存的时候,当这个DirectByteBuffer被设为null的时候,你怎么回收堆外这个内存呢?你可以用虚引用,当我们检测到这个虚引用被垃圾回收器回收的时候,你做出相应处理去回收堆外内存。

 说不定将来的某一天,你写了一个Netty,然后你再Netty里边分配内存的时候,用的是堆外内存,那么堆外内存你又想做到自动的垃圾回收,你不能让人家用你API的人,让人家自己去回收对不对?所以你这个时候怎么做到自动回收呢?你可以检测虚引用里的Queue,什么时候Queue检测到DirectByteBuffer(直接内存)被回收了,这个时候你就去清理堆外内存,堆外内存怎么回收呢? 你如果是C和C++语言写的虚拟机的话,当然是del和free这个两个函数,它们也是C和C++提供的,java里面现在也提供了,堆外内存回收,这个回收的类叫Unsafe,这个类在JDK1.8的时候可以用java的反射机制来用它,但是JDK1.9以后它被加到包里了,普通人是用不了的,但JUC的一些底层有很多都用到了这个类,这个Unsafe类里面有两个方法,allocateMemory方法直接分配内存也就是分配堆外内存,freeMemory方法回收内存也就是手动回收内存,这和C/C++里边一样你直接分配内存,必须得手动回收。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/617119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【jupyter】Jupyter Notebook如何导入导出文件

目录 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 2.Jupyter Notebook导入文件 3.Jupyter Notebook导出文件 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 1&#xff09;下载【Anaconda】后&#xff0c;直接点击【Jupyter Notebook】即可在网页打开 Jupyte…

用户研究干货——这一篇就够啦

一、基本概念&#xff1a; ①工作内容&#xff1a;用户研究的首要目的是帮助企业定义产品目标用户群&#xff0c;明确、细化产品概念&#xff0c;并通过对用户的任务操作特性、知觉特征、认知心理特征的研究&#xff0c;使用户的实际需求成为产品设计的导向&#xff0c;使产品…

建面超72万㎡,南山红花岭旧改规划公示,配套近15万㎡宿舍

近日&#xff0c;深圳市南山区城市更新和土地整备局发布关于桃源街道红花岭工业南区更新单元&#xff08;暂定名&#xff09;03-01、02-02地块《建设工程规划许可证》及总平面图的公告。 此次批复的红花岭工业南区02-02、03-01块&#xff0c;总建面超72万㎡&#xff0c;用地单…

nginx+tomcat 负载均衡、动静分离集群

文章目录 一、NginxTomcat负载均衡的组合原因1.1 Nginx实现负载均衡的原理1.2 Nginx实现负载均衡的主要配置项1.3 NginxTomcat负载均衡的组合的优点1.4 NginxTomcat负载均衡的实验设计 二、动静分离部署2.1 部署TOMCAT后端服务器2.2部署nginx服务器2.3安装nginx动态服务器 一、…

java中try-with-resources自动关闭io流

在传统的输入输出流处理中&#xff0c;我们一般使用的结构如下所示&#xff0c;使用try - catch - finally结构捕获相关异常&#xff0c;最后不管是否有异常&#xff0c;我们都将流进行关闭处理&#xff1a; try {//todo } catch (IOException e) {log.error("read xxx f…

《Lua程序设计》--学习1

前言&#xff1a; --> 表示一条语句的输出或表达式求值的结果 -- 单行注释 > 标注 一些代码需要在交互模式下输入 如果需要打印表达式求值的结果&#xff0c;必须在每个表达式前加上一个等号 <--> 表示两者完全等价 语言基础 我们将Lua语言执行的每一…

html选择器

基本选择器 基本选择器 : 标签选择器 , 类选择器 , ID选择器 标签选择器 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEed…

小企业软件项目流程六步法

小企业软件项目流程六步法&#xff0c;很有效 软件项目的沟通成本是巨大的 软件生产是非常特殊的一套流程 没有过程控制&#xff0c;最终一定失控或废弃 趣讲大白话&#xff1a;输入垃圾&#xff0c;输出也是垃圾 【趣讲信息科技188期】 **************************** 软件行业…

九、(补充文章四)Arcgis实现深度学习训练样本数据的批量制作——只靠原图+shp如何批量制作样本图片

之前写了一些个深度学习系列文 其中先是单张样本的制作方法 最后通过构造模型批量处理 大大提高了生成样本的速度 四、Arcgis实现深度学习河流训练样本数据的制作(使用软件批量获取样本图片)——对已经获取到的完整面状样本数据进行处理 但是这个方法不仅仅需要shp和原图 还需要…

在不到200行的HTML代码中,实现老板要求为他的孩子绘制一个童话乐园:七彩彩虹、微笑笑脸和魔法树

文章目录 准备工作1.绘制七彩彩虹2.绘制微笑笑脸3.绘制多变的魔法树 结语 欢迎来到童话乐园&#xff01;这里有一些有趣的绘图功能&#xff0c;让你在代码的世界中感受童话般的乐趣。本篇博文将介绍如何使用代码来绘制七彩彩虹、微笑笑脸和魔法树。让我们一起来探索吧&#xff…

vector 练习

目录 一、创建动态二维数组的方法 0x01 C语言法 0x02 C法 二、 杨辉三角 三、电话号码的数字组合 一、创建动态二维数组的方法 0x01 C语言法 int** p (int**)malloc(sizeof(int*) * M);//创建M行的数组,每一行都是一个数组 for(size_t i 0;i < M;i) {p[i] (int*)mal…

Redis主从集群与哨兵集群

一、Redis 哨兵集群原理 Redis 哨兵集群是一种高可用性的解决方案&#xff0c;用于监控 Redis 实例的状态并在实例出现故障时自动进行故障转移。 Redis 哨兵集群由多个哨兵实例组成&#xff0c;每个哨兵实例都运行在独立的服务器上。每个哨兵实例都会周期性地向 Redis 实例发…

Linux内存初始化-启动阶段的内存初始化

本文代码基于ARM64平台, Linux kernel 5.15 在加载kernel 之前&#xff0c; kernel对于系统是有一定要求的&#xff0c;明确规定了boot阶段必须要把MMU关闭&#xff1a; arch/arm64/kernel/head.S/** Kernel startup entry point.* ---------------------------** The require…

路径规划算法:基于松鼠优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于松鼠优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于松鼠优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法松鼠…

入门大数据就业前景怎么样?

时势造英雄&#xff0c;对个人而言亦是如此。跟随趋势&#xff0c;找准自己未来发力的赛道&#xff0c;在合适的时间干合适的事&#xff0c;就是抓住自己的未来。 猎聘大数据研究院发布了《2022未来人才就业趋势报告》 从排名来看&#xff0c;2022年1-4月各行业中高端人才平均…

mac m1/m2 安装 ps 2023 插件无法显示扩展界面

碎碎念&#xff1a;一直在踩坑的路上&#xff0c;甚至想休息时间玩一会儿 ps 都能踩坑 问题描述 新的 m2 芯片 mac 安装了色环插件后&#xff0c;在窗口界面中没有找到扩展&#xff0c;且在首选项->增效工具的旧版扩展也是灰色的 题外话&#xff1a;记录一下 mac 的 photo…

2023尚上优选-社区团购 优选电商Spring Cloud Alibaba

尚上优选2023最新企业级微服务架构项目 分布式微服务后端VUE、小程序 尚上优选是真实居住社区内居民团体的一种互联网线上线下购物消费行为&#xff0c;是依托真实社区的一种区域化、小众化、本地化、网络化的团购形式。简而言之&#xff0c;它是依托社区和团长社交关系实现生…

DHCP与DHCPv6讲解

目录 DHCP DHCP端口号 DHCP报文 DHCP工作过程 DHCP租期续租 DHCPv6 DHCPv6端口号 DHCPv6报文 DHCPv6工作原理 DHCP DHCP端口号 DHCP主要有两个端口号&#xff0c;分别是UDP67和UDP68 DHCP客户端向DHCP服务器发送报文时采用68端口号&#xff0c;DHCP服务器向DHCP客户端…

整合开源治理经验,共谋开源社区发展|2023 开放原子全球开源峰会开源社区治理与运营分论坛即将启幕

在数智时代广泛连接、同步演进和网状协作特性的催化下&#xff0c;开源社区正在成为技术应用和行业数字化发展的重要推动力量。开展数字技术开源社区的有效治理&#xff0c;对调和相互冲突的内外部需求、协调相互竞合的参与主体、整合差异化的绩效目标具有重要理论和实践意义。…

ChatGPT 时代,程序员的生存之道

ChatGPT 近期炙手可热&#xff0c;仿佛没有什么问题是它不能解决的。出于对 ChatGPT 的好奇&#xff0c;我们决定探索下它对于前端开发人员来讲&#xff0c;是作为辅助工具多一些&#xff0c;还是主力工具更多一些&#xff1f; 2D 能力测试 我们就挑选一个著名的递归回溯问题—…