Java面试---Java中的引用类型有哪几种

1. 前言

Java中的引用有强引用软引用弱引用虚引用四种引用类型。任何一种引用最终都会被垃圾回收机制(GC)回收,也就是说,当一个对象永久的失去引用后,就会变成垃圾,等待系统垃圾回收机制进行回收。

先来了解一下GC是怎样工作的:

  • 垃圾回收机制只会回收堆内存中的对象,不会回收物理资源;
  • 垃圾回收机制是由系统控制的,每隔固定的时间就执行一次释放操作,当一个对象永久的失去引用后,会由可达状态变为可恢复状态;
  • 系统在进行垃圾回收之前,会调用finalize()方法,这个方法可能会使对象被重新引用,变为可达状态,此时垃圾回收就会取消;
  • 当调用了所有的finalize()方法后,垃圾回收还是没有取消,该对象就会由可恢复状态转变为不可达状态,最终被系统作为垃圾回收掉该对象所占用的资源。

上面的过程中提到了三种状态,下面我们先来解释一下:

  • 可达性:当一个对象被创建后(即被new出来之后),只要有一个以上的引用变量引用它,就是可达状态;
  • 可恢复性:当一个对象失去所有引用后,就进入了可恢复状态;
  • 不可达性:也就是当调用所有finalize()方法之后。处于不可达的状态,就会等待系统随时回收掉其所占有的资源。

2. 四种引用类型

2.1 强引用

  1. 定义:强引用一般就是指被new出来的对象,如:(Object obj = new Object()),这是Java中最普遍的。只要强引用存在,垃圾回收器就不会回收被引用的对象

  2. 测试

    1)对finalize()方法进行重写,目的是为了方便查看结果;

    @Slf4j
    public class Student {
        @Override
        protected void finalize() throws Throwable {
            log.info("调用finalize()方法");
            super.finalize();
        }
    }
    

    2)编写测试类

    @Slf4j
    public class Test {
        public static void main(String[] args) throws IOException {
            Student student = new Student();
            System.gc();
            log.info(student.toString());
            System.in.read();//阻塞main线程,给垃圾回收线程时间执行
        }
    }
    /**
     * [main] INFO com.glw.thread.referenceType.强引用.Test - com.glw.thread.referenceType.强引用.Student@46fbb2c1
     */
    

    从以上的结果我们看到Student对象并没有被回收。那么如何使强引用类型被回收呢???答案是只要将对象设为null即可。

    3)测试强引用类型被垃圾回收器回收

    public class Test {
        public static void main(String[] args) throws IOException {
            Student student = new Student();
            student = null;
            System.gc();
            System.out.println("student:" + student);
            System.in.read();//阻塞main线程,给垃圾回收线程时间执行
        }
    }
    /**
     * student:null
     * [Finalizer] INFO com.glw.thread.referenceType.强引用.Student - 调用finalize()方法
     */
    

2.2 软引用

  1. 定义:软引用用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统就会回收软引用对象(即内存够用就保留,不够用就丢弃)。如果回收了软引用对象仍然没有足够的内存,才会抛出内存溢出异常。这种技术常常被用来实现缓存技术。在JDK1.2之后,用java.lang.ref.SoftReference类来表示软引用。

  2. 测试

    那么强引用和软引用有什么区别呢,我们以具体的代码体现,如下:

    在运行代码之前,我们需要先修改一下配置参数,将虚拟机的最大内存设为10M。

    在这里插入图片描述

    public class Test10M {
        public static void main(String[] args) {
            strongReference();
    
        }
        private static void strongReference(){
            byte[] bytes = new byte[1024*1024*5];//创建5M的字节数组,此时虚拟机的最大内存为10M
            System.out.println(bytes);
        }
    }
    /**
     * [B@1be6f5c3
     */
    

    此时我们创建了5M的字节数组,没有超过虚拟机内存,证明此时程序正常运行。那么当我们创建10M大小的字节数据呢???

    public class Test10M {
    
        public static void main(String[] args) {
            strongReference();
    
        }
        private static void strongReference(){
            byte[] bytes = new byte[1024*1024*10];//创建5M的字节数组,此时虚拟机的最大内存为10M
            System.out.println(bytes);
        }
    }
    /**
     * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
     * at com.glw.thread.referenceType.强引用.Test10M.strongReference(Test10M.java:15)
     * at com.glw.thread.referenceType.强引用.Test10M.main(Test10M.java:11)
     */
    

    那么此时就会报错“OutOfMemoryError”,即内存不足错误,可以证明强引用类型即使内存不够也不会被回收,同时会报出内存不足的错误,那么我们来看看软引用类型会是什么情况?

    public class Test10M {
    
        public static void main(String[] args) {
            softReference();
        }
    
        private static void softReference(){
            SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 5]);
            System.out.println("softReference:"+softReference.get());
            System.gc();
            try { //进行睡眠让垃圾回收有时间执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("softReference:"+softReference.get());
    
            byte[] bytes = new byte[1024 * 1024 * 5];
            System.out.println("softReference:"+softReference.get());
    
        }
    }
    /**
     * softReference:[B@1be6f5c3
     * softReference:[B@1be6f5c3
     * softReference:null
     */
    
    

    此时我们的虚拟机的最大内存仍然为10M,此时我们创建了5M大小的软引用类型的对象,对于此时来说,内存还是足够的,但当我们执行到下面创建了一个5M大小的强引用类型的对象,我们知道,此时的内存是不够的,强引用类型的对象我们在上面验证过是不会被回收的,那么,最后的结果就是软引用类型的对象被回收,我们就能得到最后软引用对象的结果为空。同时也验证了上面的理论,当内存足够用时,软引用是不会被回收的,但是当内存不够时,软引用对象就会被内存回收。

2.3 弱引用

  1. 定义:弱引用的引用强度比软引用更弱一些,无论内存是否足够,只要JVM开始进行垃圾回收,那些被弱引用关联的对象都会被回收。即“只要发生GC,一定被回收”。

  2. 测试:

    ThreadLocal和WeakHashMap内部都是使用了弱引用,用来保证那些不被用到的key值,在垃圾回收的时候可以被回收掉。下面我们以ThreadLocal为例来解析内部的使用:

    ThreadLocal的主要原理是找到当前线程的map;Map的结构为Map(key,value),key为ThreadLocal本身value设的数据;key使用弱引用,解决内存泄露,使用remove,解决value内存泄露;

    内存泄露(memory leak):内存够用,但里面有一个对象总是不能被回收(例如:强引用),位置占用。

    内存溢出(out of memory):大量的内存泄露会导致内存溢出。

    ThreadLocal<Person> tl = new ThreadLocal<>();
            tl.set(new Person());  //此时的new Person会被Map读到
            tl.remove();   //remove主要为了移除value,解决value内存泄露问题
    

    在上面的代码中解释了set方法的原理,以及remove的作用,在这里我们能看出ThreadLocal是一个容器,可以用来new出一个对象,但是是线程的私有容器,只能被一个线程读到。那么我们来看看set的主要实现:

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);  //t.threadLocals,拿取自己的key,value
            if (map != null)
                map.set(this, value);  //this指的是当前的ThreadLocal对象
            else
                createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
    }
    
    public class Test {
        static ThreadLocal<Person> tl = new ThreadLocal<>();
    
        public static void main(String[] args) {
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tl.set(new Person("zhangsan"));
                System.out.println(tl.get());
    //            tl.remove();  //把tl记录从map中删掉,(线程池中一定要remove掉,否则会导致内存泄露问题)
            }).start();
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(tl.get());
            }).start();
        }
    }
    
    /**
     * Person(name=zhangsan)
     * null
     */
    

    此例子验证了ThreadLocal是线程的私有容器,只能被一个线程读到。

在这里插入图片描述

上面我们说到Entry使用的是弱引用,那么为什么Ehtry使用的是弱引用呢???

若是使用强引用,即使t1=null,使key的引用依然指向ThreadLocal对象,所以会有内存泄露,而使用弱引用不会。但还是有内存泄漏存在,ThreadLocal被回收,key的值变成null,则导致整个value再也无法被访问到,因此依然存在内存泄露。这就是为什么在线程池中使用完需要remove的原因。

2.4 虚引用

  1. 定义:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,随时可能会被回收。JDK 1.2 之后,使用 java.lang.ref.PhantomReference 表示虚引用,这个类只有构造器和一个 get() 方法,且 get() 方法仅返回一个 null 值。
    在这里插入图片描述
    我们无法通过虚引用获得对象,必须要和ReferenceQueue引用队列一起使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

  2. 测试

    public class PhantomReferenceTest {
        public static void main(String[] args) {
            ReferenceQueue<String> queue = new ReferenceQueue<String>();
            PhantomReference<String> pr = new PhantomReference<String>(new String("yamiyami"), queue);
            System.out.println(pr.get());
        }
    }
    /**
     * null
     */
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是几个与Java八种基本数据类型和对应的包装类型相关的面试题: 1. Java有哪八种基本数据类型?它们分别是什么? Java有8种基本数据类型,分别是:byte、short、int、long、float、double、char和boolean。 2. Java的基本数据类型和包装类型有什么区别? 基本数据类型直接存储数据的值,而包装类型是基于基本数据类型的对象类型,它们提供了一些额外的功能,如自动装箱和自动拆箱、null值、equals方法等。 3. 什么是自动装箱和自动拆箱? 自动装箱是指将基本数据类型转换为对应的包装类型,自动拆箱是指将包装类型转换为对应的基本数据类型。Java 5引入了自动装箱和自动拆箱的特性,使得开发者无需手动转换,而是由编译器自动完成转换的工作。 4. 如何将基本数据类型转换为对应的包装类型? 可以使用对应的包装类型的valueOf方法进行转换。例如,将int类型的值转换为Integer类型可以使用Integer.valueOf(int)方法。 5. 如何将包装类型转换为对应的基本数据类型? 可以使用包装类型的xxxValue方法进行转换,其xxx是对应的基本数据类型的名称。例如,将Integer类型的对象转换为int类型可以使用intValue()方法。 6. Integer i = 10 和 Integer i = new Integer(10) 有什么区别? 前者使用自动装箱将int类型的值10转换为Integer类型,而后者是通过显式地创建一个Integer对象来实现的。 7. 为什么应该使用包装类型而不是基本数据类型? 使用包装类型可以提供更多的功能,如自动装箱和自动拆箱、null值、equals方法等。此外,某些框架和库要求使用包装类型,例如Hibernate的映射。 8. 为什么应该避免使用包装类型? 使用包装类型会带来一些额外的开销,如对象创建、内存分配和垃圾回收等,对性能有一定的影响。因此,在性能要求较高的场景下,应该尽量使用基本数据类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值