Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(上)

简介: Java中的引用类型(强引用、软引用、弱引用、虚引用)介绍,示例WeakHashMap的使用【享学Java】(上)

前言


Java语言中的数据类型可划分为值类型和引用类型。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。

这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。


值类型和引用类型


  • 值类型:byte、short、int、long、float、double、char、boolean
  • 引用类型:除了值类型,所有的类型都是引用类型。(数组、字符串、类、接口等)


一个具有值类型的数据存放在栈内的一个变量中:栈内分配内存空间,直接存储所包含的值,其值代表的数据本身。

而引用类型数据的变量值会存放在堆中,变量名(引用地址)会存放在栈中。


堆区: 1、存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 2、jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

栈区:1、每个线程包含一个栈区,栈中只保存基础数据类型(值就是本身)的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问(所以也有人说栈是保存局部变量的)。 3、栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

方法区:又叫静态区。1、跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 2、方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。


在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。

栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。(so,实例基本变量存在哪儿呢?所以:它显然不会是存在栈里的)


栈里存储的变量都是瞬时的(方法执行完就死了),而堆里的对象靠垃圾回收(年轻代、永久代(元空间))


Java引用类型


在Java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用。


// @since    1.2   所在包是:java.lang.ref
public abstract class Reference<T> {
  // 构造函数是非public的  所以只能通包内的类才能访问此构造函数
    Reference(T referent) {
        this(referent, null);
    }
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
  ...
    public T get() {
        return this.referent;
    }
    public void clear() {
        this.referent = null;
    }
    public boolean isEnqueued() {
        return (this.queue == ReferenceQueue.ENQUEUED);
    }
    public boolean enqueue() {
        return this.queue.enqueue(this);
    }
  ...
}


直接子类,只有下面这四个:


image.png


Reference<T>是JDK1.2提供的引用类型,它有四个子类分别代表着四种引用类型。其中只有强引用FinalReference类是包内可见,其余的都是public的。这也侧面表示了:强引用类型是Java默认的引用类型,直接使用即可


其实FinalReference根本就不是强引用,网上很多文章都以讹传讹,问下我有勘误。


强引用


强引用( Final Reference):只要强引用还存在,垃圾收集器永远不会回收(JVM宁愿抛出OOM异常也不回收强引用所指向的对)被引用的对象。但是,强引用可能会造成内存泄露哦


public class Main {
    public static void main(String[] args) throws Exception {
        Person person = new Person("fsx", 18);
        System.out.println(person); //强引用:直接使用
    }
}


这就是强引用的典型示例,我们平时绝大多数情况下都使用强引用即可。


强引用的特点:


  1. 可以直接访问目标对象。
  2. 所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
  3. 可能导致内存泄漏。

若你的内存够大,无需考虑极致性能,所有引用都使用强引用也是ok的;强引用可直接访问,是Java默认的引用方式~


注意:我看到很多文章把FinalReference解释为强引用,这是错误的。

/**
 * Final references, used to implement finalization
 */
class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}


从它的javadoc也能看出来,这个类它和强引用没半毛钱关系。它倒是和Object的finalize()方法有关。关于它的详细介绍,可参阅笨神的大作:JVM源码分析之FinalReference完全解读


关于finalize方法的使用,亦可参阅这篇文章:java finalize方法总结、GC执行finalize的过程


软引用

软引用(Soft Reference):是用来描述一些还有用但并非必须的对象。对于软引用对象,如果内存充足(注意此处对充足二字的理解)gc不会管它,如果内存不够了,它就不能幸免了


public class SoftReference<T> extends Reference<T> {
    static private long clock;
    private long timestamp;
  // 提供了两个构造函数 可以构造软引用
    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }
  // 获取此软引用的对象
    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
}


软引用是除了强引用外最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。


SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。

也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。一旦垃圾线程回收该Java对象之后,get()方法将返回null。


很多初学者不太能理解这块:一言以蔽之,它就是创建一个SoftReference对象,然后这个对象里面包装了obj(强引用对象),也就是对它实施了管理,最终实际存在的是两个对象哦(SoftReference自己变成了强引用,请务必注意这点)~


public class Main {
    public static void main(String[] args) throws InterruptedException {
        Person obj = new Person("fsx", 18);
        SoftReference sf = new SoftReference<>(obj);
        obj = null; //置为null 让obj被垃圾回收期回收
        //byte[] bytes = new byte[1024 * 100];
        //System.gc();
        //TimeUnit.SECONDS.sleep(1); // 把问题放大,让gc过来回收 确保GC肯定会执行
        System.out.println("是否被回收:" + sf.get());
    }
}

运行时请调整虚拟机参数为:-Xmx2m -Xms2m规定堆内存大小为2m。


image.png

运行结果:


是否被回收:Person{name='fsx', age=18}


结果说明:当obj=null后,这时,Obj对象就只剩下软引用了,是软可达的。所以哪怕这个时候调用System.gc();也是不会被回收的(=null后置表示失去了obj这个引用,但是sf还软引用着你呢~~~)


打开被注释掉的new byte[1024*100]语句:这条语句请求一块大的堆空间,使堆内存使用紧张。并显式的再调用一次GC,结果如下:


是否被回收:null


obj对象已经被回收,软可达已经变成了不可达,这就是软引用的效果~


此处不知你是否还能看出一个问题:

obj被gc回收后,SoftReference sf这个对象就成垃圾了,完全没用了,这个时候建议你处理掉它,否则可能造成内存泄漏现象(得不偿失呀)。所以ReferenceQueue队列上场啦(重要:生产环境中一般建议配合队列一起使用):


ReferenceQueue队列和JVM对象垃圾回收机制有关,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference

相关文章
|
12天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
1月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
44 1
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
73 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
1月前
|
存储 Java
什么是带有示例的 Java 中的交错数组?
什么是带有示例的 Java 中的交错数组?
47 9
|
1月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
27 1
|
1月前
|
Java API 网络安全
Java 发送邮件示例
本示例展示了如何使用Java编程语言发送电子邮件。通过利用JavaMail API,这段代码实现了从配置SMTP服务器,设置邮件属性,到发送邮件的全过程,为开发者提供了实用的参考。
|
1月前
|
存储 Java 程序员
【一步一步了解Java系列】:何为数组,何为引用类型
【一步一步了解Java系列】:何为数组,何为引用类型
25 1
|
30天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
21 0
|
9天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
8天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
下一篇
无影云桌面