java中的引用

简介: ## 介绍JAVA 中有 4 种类型的引用:– **强引用**– **软引用**– **弱引用**– **幻像引用**这些引用的区别仅在于**垃圾回收器**管理它们的方式。如果你从未听说过它们,这意味着你只使用强大的。了解差异可以帮助您,特别是如果您需要存储临时对象并且无法使用真正的缓存库(如eHcache或Guava)。由于这些类型与 JVM 垃圾回收器密切相关,因此我将简要回顾一下有关 JAVA 中垃圾回收的一些信息,然后介绍不同的类型。[TOC] ## 垃圾回收器Java和C++之间的主要区别在于**内存管理**。在Java中,开发人员不需要知道内存是

介绍

JAVA 中有 4 种类型的引用:强引用软引用弱引用幻像引用

这些引用的区别仅在于垃圾回收器管理它们的方式。如果你从未听说过它们,这意味着你只使用强大的。了解差异可以帮助您,特别是如果您需要存储临时对象并且无法使用真正的缓存库(如eHcache或Guava)。

由于这些类型与 JVM 垃圾回收器密切相关,因此我将简要回顾一下有关 JAVA 中垃圾回收的一些信息,然后介绍不同的类型。

介绍垃圾回收器问题强力引用软引用弱引用幻像引用结论


垃圾回收器

Java和C++之间的主要区别在于内存管理。在Java中,开发人员不需要知道内存是如何工作的(但他应该知道!),因为JVM通过其垃圾回收器来处理这一部分。

创建对象时,它由 JVM 在其中分配。堆是内存中的有限空间量。因此,JVM 通常需要删除对象才能释放空间。要销毁对象,JVM 需要知道此对象是处于活动状态还是非活动状态。如果对象被垃圾回收root引用(传递),则该对象仍在使用中。

例如:

  • 如果对象 C 由对象 B 引用,B 由对象 A 引用,A 由垃圾回收root引用,则 C、B 和 A 被视为活动(情况 1)。
  • 但是,如果 A 不再引用 B,则 C 和 B 不再处于活动状态,可以销毁(案例 2)。


由于这篇文章不是关于垃圾收集器的,我不会在解释中深入介绍,但仅供参考,有4种类型的垃圾收集root:

  1. 局部变量
  2. 活动 Java 线程
  3. 静态变量
  4. JNI 引用,这些 Java 对象包含本机代码,而不是由 jvm 管理的内存

Oracle 没有指定如何管理内存,因此每个 JVM 实现都有自己的一组算法。但这个想法总是一样的:- JVM使用一种递归算法来查找非活动对象并标记它们- 标记的对象被最终确定(调用finize()方法)然后销毁- JVM有时会移动剩余对象的一部分,以便在堆中重建大面积的自由连续空间


问题

如果JVM管理内存,你为什么需要关心?因为这并不意味着你不能有内存泄漏

大多数情况下,您在没有意识到的情况下使用垃圾回收根目录。例如,假设您需要在程序的生命周期内存储一些 objet(因为它们的初始化成本很高)。您可能会使用静态整理(List、Map 等)来存储和检索代码中任何位置的这些对象:

publicstaticMap<K, V>myStoredObjects=newHashMap<>();

但是,通过这样做,可以防止 JVM 破坏集合中的对象。通过错误,你可能会和一个超出记忆的错误。例如:

publicclassOOM {

   publicstaticList<Integer>myCachedObjects=newArrayList<>();

 

   publicstaticvoidmain(String[] args) {

       for (inti=0; i<100_000_000; i++) {

           myCachedObjects.add(i);

       }

   }

}

输出为:

线程“main” java.lang.OutOfMemoryError中的异常:Java heap space


Java提供了不同类型的引用来避免OutOfMemoryError。

某些类型允许 JVM 释放对象,即使程序仍然需要这些对象。开发人员有责任处理这些情况。


强力引用

强参考是标准参考。当你在对象 obj 上创建这样的内容时:

MyClassobj=newMyClass ();

您正在创建一个名为“obj”的强引用,该引用指向新创建的 MyClass 实例。当垃圾回收器查找非活动对象时,它只检查 objets 是否强可访问,这意味着通过强引用传递链接到垃圾回收根。

使用此类型的引用会强制 JVM 将对象保留在堆中,直到未按照“垃圾回收器”一节中所述使用对象为止。


软引用

根据java API的软参考有:

“软引用对象,由垃圾回收器自行决定清除,以响应内存需求”

这意味着,如果您在不同的JVM上运行程序,软引用的行为可能会改变(Oracle的Hotspot,Oracle的JRockit,IBM的J9等)。


让我们来看看Oracle的JVM Hotspot(标准和最常用的JVM),看看它如何管理软引用。根据甲骨文文档:

“默认值为每兆字节 1000 毫秒,这意味着对于堆中每 MB 的可用空间,软引用将存活 1 秒(在收集了对对象的最后一个强引用之后)”

下面是一个具体示例:让我们假设堆是 512 MB,有 400MB 可用。

我们创建一个对象 A,软引用对象缓存,强引用 A 到对象 B。由于 A 强烈引用 B,因此它非常可访问,并且不会被垃圾回收器删除(情况 1)。

想象一下,现在 B 已被删除,因此 A 仅被软引用到缓存对象。如果对象 A 在接下来的 400 秒内未被强烈引用,则会在超时后将其删除(情况 2)。

网络异常,图片无法展示
|



以下是操作软引用的方法:

publicclassExampleSoftRef {

   publicstaticclassA{

 

   }

   publicstaticclassB{

       privateAstrongRef;

 

       publicvoidsetStrongRef(Aref) {

           this.strongRef=ref;

       }

   }

   publicstaticSoftReference<A>cache;

 

   publicstaticvoidmain(String[] args) throwsInterruptedException{

       //用软instanceA的引用的缓存的初始化

       ExampleSoftRef.AinstanceA=newExampleSoftRef.A();

       cache=newSoftReference<ExampleSoftRef.A>(instanceA);

       instanceA=null;

       // 现在instanceA是只软可到达的且可以在一段时间后,由垃圾回收器删除

       Thread.sleep(5000);

 

       ...

       ExampleSoftRef.BinstanceB=newExampleSoftRef.B();

       //从缓存具有软引用的实例后,我们不能肯定instanceA仍然存在

       //如果需要,我们需要检查并重新创建instanceA

       instanceA=cache.get();

       if (instanceA==null){

           instanceA=newExampleSoftRef.A();

           cache=newSoftReference<ExampleSoftRef.A>(instanceA);

       }

       instanceB.setStrongRef(instanceA);

       instanceA=null;

       // instanceA现在只是轻轻地由缓存引用和强烈引用,所以它不能是clea

 

       ...

   }

}

但是,即使垃圾回收器自动删除了软引用对象,软引用(也是对象)也不会被删除!因此,您仍然需要清除它们。例如,对于像 64 MB (Xmx64m) 这样的低堆大小,下面的代码给出了一个 OutOfMemoryException,尽管使用了软引用。

publicclassTestSoftReference1 {

 

   publicstaticclassMyBigObject{

       //each instance has 128 bytes of data

       int[] data=newint[128];

   }

   publicstaticintCACHE_INITIAL_CAPACITY=1_000_000;

   publicstaticSet<SoftReference<MyBigObject>>cache=newHashSet<>(CACHE_INITIAL_CAPACITY);

 

   publicstaticvoidmain(String[] args) {

       for (inti=0; i<1_000_000; i++) {

           MyBigObjectobj=newMyBigObject();

           cache.add(newSoftReference<>(obj));

           if (i%200_000==0){

               System.out.println("size of cache:"+cache.size());

           }

       }

       System.out.println("End");

   }

}

输出代码为:

缓存大小:1缓存大小:200001缓存大小:400001缓存大小:600001线程“主”中的异常 java.lang.OutOfMemoryError: 超出 GC 开销限制

Oracle 提供了一个 ReferenceQueue,当引用的对象只能软访问时,该队列会填充软引用。使用此队列,您可以清除软引用并避免超出内存错误。

使用 ReferenceQueue,与上面相同的代码具有相同的堆大小(64 MB),但要存储的数据更多(500 万对 100 万),可以工作:

publicclassTestSoftReference2 {

   publicstaticintremovedSoftRefs=0;

 

   publicstaticclassMyBigObject {

       //each instance has 128 bytes of data

       int[] data=newint[128];

   }

 

   publicstaticintCACHE_INITIAL_CAPACITY=1_000_000;

   publicstaticSet<SoftReference<MyBigObject>>cache=newHashSet<>(

           CACHE_INITIAL_CAPACITY);

   publicstaticReferenceQueue<MyBigObject>unusedRefToDelete=newReferenceQueue<>();

 

   publicstaticvoidmain(String[] args) {

       for (inti=0; i<5_000_000; i++) {

           MyBigObjectobj=newMyBigObject();

           cache.add(newSoftReference<>(obj, unusedRefToDelete));

           clearUselessReferences();

       }

       System.out.println("End, removed soft references="+removedSoftRefs);

   }

 

   publicstaticvoidclearUselessReferences() {

       Reference<?extendsMyBigObject>ref=unusedRefToDelete.poll();

       while (ref!=null) {

           if (cache.remove(ref)) {

               removedSoftRefs++;

           }

           ref=unusedRefToDelete.poll();

       }

 

   }

}

输出为:

结束,删除的软引用 = 4976899

当您需要存储许多对象时,软引用非常有用,如果这些对象被 JVM 删除,这些对象可能会(代价高昂)地重新实例化。


弱引用

弱参考是一个比软参考更易失性的概念。根据JAVA API:

“假设垃圾回收器在某个时间点确定某个对象是弱可访问的。届时,它将原子清除对该对象的所有弱引用,以及对任何其他弱可访问对象的所有弱引用,该对象可通过强引用和软引用链访问。同时,它将声明所有以前弱可访问的对象都是可最终确定的。在同一时间或稍后的某个时间,它将把那些新清除的弱引用排入参考队列。

这意味着,当垃圾回收器检查所有对象时,如果它检测到的对象仅具有对垃圾回收根的弱引用(即没有链接到该对象的强引用或软引用),则该对象将被标记为删除并尽快删除。使用弱引用的方式与使用软引用完全相同。因此,请看部分“软引用”中的示例。

Oracle提供了一个基于弱引用的非常有趣的类:WeakHashMap。此映射具有具有弱引用键的特殊性。WeakHashMap可以用作标准Map。唯一的区别是,它将在从堆中销毁密钥后自动清除:

publicclassExampleWeakHashMap {

   publicstaticMap<Integer,String>cache=newWeakHashMap<Integer, String>();

 

   publicstaticvoidmain(String[] args) {

       Integeri5=newInteger(5);

       cache.put(i5, "five");

       i5=null;

       //输入{5,"five"}将留在地图,直到下一个垃圾回收器调用

 

       Integeri2=2;

       //输入{2,"two"}将留在地图,直到i2强引用

       cache.put(i2, "two");

 

       //remebmber内存溢出错误在第二章"problem",这次不会发生

       //因为Map,将清除它的条目。

       for (inti=6; i<100_000_000; i++) {

           cache.put(i,String.valueOf(i));

       }

   }

}

例如,我使用 WeakHashMap 解决了以下问题:存储事务的多个信息。我使用了这个结构:WeakHashMap<String,Map<K,V>>其中,WeakHashMap的键是一个包含事务Id的字符串,而“简单”Map是我在事务生命周期内需要保留的信息。有了这个结构,我肯定会在WeakHashMap中获取我的信息,因为包含事务ID的字符串在事务结束之前无法销毁,我不必关心清理Map。

Oracle建议使用WeakHashMap作为“规范化”映射。


幻像引用

在垃圾回收过程中,没有对垃圾回收根的强/软引用的对象将被删除。在删除之前,将调用方法 finalize()。当对象最终确定但尚未删除时,它将变为“幻像可访问”,这意味着对象和垃圾回收根之间只有幻像引用。

与软引用和弱引用不同,对对象使用显式幻像引用可防止删除对象。程序员需要显式或隐式删除幻像引用,以便可以销毁最终确定的对象。要显式清除幻像引用,程序员需要使用 ReferenceQueue,该队列在对象最终确定时用幻像引用填充。

幻像引用无法检索被引用的对象:幻像引用的 get() 方法始终返回 null,以便程序员无法使幻像可访问对象再次强/软/弱可访问。这是有道理的,因为幻像可访问对象已经完成,因此如果例如覆盖的finize()函数已清除资源,则它不再起作用。

我不明白幻像引用如何有用,因为无法访问引用的对象。一个用例可能是,如果您需要在对象最终确定后执行操作,并且您无法(或出于性能原因不想)在此对象的fineize()方法中执行特定操作。


结论

我希望您现在对这些参考资料有了更好的了解。大多数情况下,您不需要显式使用它们(也不应该使用它们)。但是,许多框架都在使用它们。如果你想了解事物是如何工作的,那么了解这个概念是件好事。

目录
相关文章
|
3月前
|
安全 Java
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
|
7月前
|
Java 测试技术
Java方法引用详解
在Java中,方法引用是一种强大的功能,它允许您在Lambda表达式中引用方法,而不是在表达式中直接定义这些方法。方法引用使代码更加简洁和可读,尤其在函数式编程中非常有用。本文将详细介绍Java中的方法引用,包括引用类方法、引用对象的实例方法、引用类的实例方法和引用构造器。
66 0
|
4天前
|
Java
Java基础之对象的引用
Java基础之对象的引用
5 0
|
1月前
|
存储 Java C语言
【Java】以数组为例简单理解引用类型变量
【Java】以数组为例简单理解引用类型变量
15 1
|
1月前
|
缓存 Java
Java四个引用
Java四个引用
|
1月前
|
Java
[java进阶]——方法引用改写Lambda表达式
[java进阶]——方法引用改写Lambda表达式
|
4月前
|
Java
java 父类引用指向子类对象
java 父类引用指向子类对象
25 0
|
4月前
|
Java
Java中引用的概念
Java中引用的概念
32 1
|
4月前
|
Java
【Java代码】使用双冒号 :: 简洁代码及方法引用(静态方法+构造方法+实例方法+函数式编程举例)
【Java代码】使用双冒号 :: 简洁代码及方法引用(静态方法+构造方法+实例方法+函数式编程举例)
28 0
|
4月前
|
存储 Java
Java之Stream流及方法引用的详细解析二
2.6Stream流综合练习【应用】 案例需求 现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作 男演员只要名字为3个字的前三人 女演员只要姓林的,并且不要第一个
51 0