百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

一文搞懂JAVA 中的引用

moboyou 2025-03-09 17:24 68 浏览

介绍

JAVA 中有 4 种类型的引用:
-
强引用
-
软引用
-
弱引用
-
虚引用

这些引用仅在垃圾收集器管理它们的方式上有所不同。如果您从未听说过它们,则意味着您只使用过强的。了解其中的区别会对您有所帮助,尤其是当您需要存储临时对象并且不能 使用像 eHcache 或 Guava 这样的真正的缓存库时。

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

垃圾收集器

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

当您创建一个对象时,它由 JVM 在其 堆中分配。堆是内存中有限的空间。因此,JVM 经常需要删除对象以释放空间。为了销毁一个对象,JVM 需要知道这个对象是活跃的还是不活跃的。如果一个对象被“垃圾收集根”引用(传递地),则该对象仍在使用中。

例如:

  • 如果对象 C 被对象 B 引用,B 被对象 A 引用,A 被垃圾回收根引用,那么 C、B 和 A 被认为是活动的(情况 1)。
  • 但是,如果 B 不再被 A 引用,则 C 和 B 不再处于活动状态并且可以被销毁(情况 2)。


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

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

Oracle 没有指定如何管理内存,因此每个 JVM 实现都有自己的一套算法。但想法总是一样的:
– JVM 使用循环算法寻找非活动对象并标记它们
– 标记的对象被终结(调用 finalize() 方法)然后被销毁
– JVM 有时会移动剩余对象的一部分为了重建堆中大面积的空闲连续空间

问题

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

大多数时候,您都在不知不觉中使用垃圾收集根。例如,假设您需要在程序的生命周期内存储一些对象(因为它们的初始化成本很高)。您可能会使用静态集合(List、Map 等)在代码中的任何位置存储和检索这些对象:

public static Map myStoredObjects= new HashMap<>();

但是,这样做可以防止 JVM 破坏集合中的对象。您可能会错误地遇到OutOfMemoryError。例如:

 public class OOM {
            public static List myCachedObjects = new ArrayList<>();
            public static void main(String[] args) {
                for (int i = 0; i < 100_000_000; i++) {
                    myCachedObjects.add(i);
                }
            }
        }

输出是:

线程“main”中的异常
java.lang.OutOfMemoryError:Java 堆空间

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

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

强引用

强引用是标准引用。当您像这样在对象 obj 上创建时:

MyClass obj = new MyClass ();

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

使用这种类型的引用会强制 JVM 将对象保留在堆中,直到对象不被使用,如“垃圾收集器”部分所述。

软引用

根据 java API soft reference 有:

“软引用对象,由垃圾收集器根据内存需求自行清除”

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

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

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

这是一个具体的例子:假设堆是 512 MB,还有 400 MB 空闲。

我们创建一个对象A,软引用到对象缓存,并强引用A到对象B。由于A被强引用到B,它是强可达的并且不会被垃圾收集器删除(案例 1)。

想象一下,现在B 被删除了,所以A只是对缓存对象的软引用。如果对象A在接下来的 400 秒内没有被强引用,它将在超时后被删除(情况 2)。


以下是如何操作软引用:

public class ExampleSoftRef {
    public static class A{
 
    }
    public static class B{
        private A strongRef;
 
        public void setStrongRef(A ref) {
            this.strongRef = ref;
        }
    }
    public static SoftReference cache;
 
    public static void main(String[] args) throws InterruptedException{
        //initialisation of the cache with a soft reference of instanceA
        ExampleSoftRef.A instanceA = new ExampleSoftRef.A();
        cache = new SoftReference(instanceA);
        instanceA=null;
        // instanceA  is now only soft reachable and can be deleted by the garbage collector after some time
        Thread.sleep(5000);
 
        ...
        ExampleSoftRef.B instanceB = new ExampleSoftRef.B();
        //since cache has a SoftReference of instance A, we can't be sure that instanceA still exists
        //we need to check and recreate an instanceA if needed
        instanceA=cache.get();
        if (instanceA ==null){
            instanceA = new ExampleSoftRef.A();
            cache = new SoftReference(instanceA);
        }
        instanceB.setStrongRef(instanceA);
        instanceA=null;
        // instanceA a is now only softly referenced by cache and strongly referenced by B so it cannot be cleared by the garbage collector
 
        ...
    }
}

但是即使软引用对象被垃圾收集器自动删除,软引用(也是对象) 也没有被删除! 所以,你仍然需要清除它们。例如,对于像 64 Mbytes (Xmx64m) 这样的小堆大小,尽管使用了软引用,但以下代码会给出 OutOfMemoryException。

public class TestSoftReference1 {
 
    public static class MyBigObject{
        //each instance has 128 bytes of data
        int[] data = new int[128];
    }
    public static int CACHE_INITIAL_CAPACITY = 1_000_000;
    public static Set<SoftReference> cache = new HashSet<>(CACHE_INITIAL_CAPACITY);
 
    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000; i++) {
            MyBigObject obj = new MyBigObject();
            cache.add(new SoftReference<>(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,当引用的对象只能软访问时,它会填充软引用。使用此队列,您可以清除软引用并避免 OutOfMemoryError。

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

public class TestSoftReference2 {
    public static int removedSoftRefs = 0;
 
    public static class MyBigObject {
        //each instance has 128 bytes of data
        int[] data = new int[128];
    }
 
    public static int CACHE_INITIAL_CAPACITY = 1_000_000;
    public static Set<SoftReference> cache = new HashSet<>(
            CACHE_INITIAL_CAPACITY);
    public static ReferenceQueue unusedRefToDelete = new ReferenceQueue<>();
 
    public static void main(String[] args) {
        for (int i = 0; i < 5_000_000; i++) {
            MyBigObject obj = new MyBigObject();
            cache.add(new SoftReference<>(obj, unusedRefToDelete));
            clearUselessReferences();
        }
        System.out.println("End, removed soft references=" + removedSoftRefs);
    }
 
    public static void clearUselessReferences() {
        Reference ref = unusedRefToDelete.poll();
        while (ref != null) {
            if (cache.remove(ref)) {
                removedSoftRefs++;
            }
            ref = unusedRefToDelete.poll();
        }
 
    }
}

输出是:

结束,删除软引用=4976899

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

弱引用

弱引用是一个比软引用更易变的概念。根据 JAVA API:

“假设垃圾收集器在某个时间点确定一个对象是 弱可达的。届时,它将自动清除对该对象的所有弱引用以及对该对象可通过强引用链和软引用链访问的任何其他弱可达对象的所有弱引用。同时,它会声明所有以前的弱可达对象是可终结的。同时或在稍后的某个时间,它会将那些在引用队列中注册的新清除的弱引用排入队列。”

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

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

public class ExampleWeakHashMap {
    public static Map cache = new WeakHashMap();
 
    public static void main(String[] args) {
        Integer i5 = new Integer(5);
        cache.put(i5, "five");
        i5=null;
        //the entry {5,"five"} will stay in the Map until the next garbage collector call
 
        Integer i2 = 2;
        //the entry {2,"two"} will stay  in the Map until i2 is no more strongly referenced
        cache.put(i2, "two");
 
        //remebmber the OutOfMemoryError at the chapter "problem", this time it won't happen
        // because the Map will clear its entries.
        for (int i = 6; i < 100_000_000; i++) {
            cache.put(i,String.valueOf(i));
        }
    }
}

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

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

虚引用

在垃圾收集过程中,没有对垃圾收集根的强/软引用的对象将被删除。在被删除之前,方法 finalize() 被调用。当一个对象被终结但没有被删除(还)时,它就变成了“幻影可达”,这意味着在对象和垃圾收集根之间只有一个幻影引用。

与软引用和弱引用不同,对对象使用显式幻像引用可以防止对象被删除。程序员需要显式或隐式地移除幻影引用,以便销毁最终化的对象。要显式清除幻影引用,程序员需要使用 ReferenceQueue ,当对象完成时,它会填充幻影引用。

幻影引用无法检索被引用的对象:幻影引用的 get() 方法始终返回 null,因此程序员无法再次使幻影可达对象强/软/弱可达。这是有道理的,因为幻影可达对象已经完成,所以如果重写的 finalize() 函数已清除资源,它可能不再工作。

由于无法访问引用的对象,因此我看不出幻影引用有何用处。一个用例可能是,如果您需要在对象完成后执行操作,而您不能(或出于性能原因不想)在该对象的 finalize() 方法中执行特定操作。

结论

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

相关推荐

iis部署php项目(iis发布php)

1.启动iis服务器最后点击确定就完成了2.打开iis点击进入即可3.创建网站进入添加网站。添加注意事项如图所示!启动、浏览、重启、停止网站这个如图所示。需要说明的是只要修改了配置就必须重启下网站4...

Win10安装Apache和PHP(apache安装php模块)

说明:虽然PHPStudy之类的软件给我们提供了方便的集成环境,但是其使用的是非线程安全的PHP版本(NotThreadSafe,NTS),某些功能不可以使用。所以,我们还需要自己再安装一个Apa...

两个php框架在一个网站上开发好吗

框架就是通过提供一个开发Web程序的基本架构,PHP开发框架把PHPWeb程序开发摆到了流水线上。换句话说,PHP开发框架有助于促进快速软件开发(RAD),这节约了你的时间,有助于创建更为稳定的程序,...

Ubuntu linux 上的 Nginx 和 Php 安装

教程-在UbuntuLinux上安装Nginx1.安装Nginx服务器和所需的包。apt-getupdatebrapt-getinstallnginx2.在Nginx配置文件...

网站开发初级(3) 之 PHP运行环境搭建

这篇文章主要是讲windows下php环境的搭建这里我们用的是phpstudy,作为入门学者足够了,解压安装就能用了,适当配置下基本能应付我们的开发要求下载地址:http://rj.baidu.co...

真实案例:优化用老PHP7.4的网站让其速度提高4倍

我让反应迟钝的PHP应用程序性能提升了4倍,而且我甚至没有改动任何源代码。没有重构,没有重写,只是进行了一系列精心规划的配置调整和部署优化。这篇文章将详细告诉你我是如何做到的。如果你的PHP应用程序运...

mac下快速搭建本地php开发环境(macbook开发php)

本教程主要是告诉大家,怎么快速的在mac下搭建php+mysql服务。一、安装CommandLineTools苹果系统下很多开发工具是基于CommandLineTools,所以安装它是首先。...

php手把手教你做网站(二十)vue+tp6简单案例(demo)

很多时候搭建好了环境,但是不知道怎么入手去开发。下面我们通过简单案例说明如何快速入门开发模块:例1:开发helloworld模块搭建好环境,新建项目以后,进入项目所在文件夹,依次进入src/compo...

Windows安装phpstudy(windows安装docker desktop)

说明:phpstudy是一个PHP+MySQL+Apache的集成环境,可以减少单独部署各个所需软件的麻烦,以及更加方便地切换版本。phpenv、wamp等软件的作用一样。由于环境的不同,安装过程中可...

服务器安装PHP网站的运行环境(php的服务器app)

首先要确认的是你的服务器的系统,Windows、或者linux系统。要想在Windows系统里运行php网站,可以选择iis或者Apache,如果你单独的去安装,可能会花很多时间去配置这个软件直接问题...

折腾群晖NAS:使用群晖web功能搭建个人博客「 小白玩NAS系列」

大家好,今天分享给大家如何利用群晖自带的web功能,来搭建一个属于自己的个人网站或者博客。搭建群晖web环境1、首先进入套件中心,下载webstation,群晖自带的web环境,安装会提示安装PHP...

黑客搭建钓鱼平台,手把手教你如何钓鱼?

跨站脚本攻击XSS:通过HTML注入篡改了网页,插入了恶意的脚本,从而用户浏览网页时,控制用户浏览器的一种攻击那么,我们搭建一个XSS钓鱼平台吧,注意:这个平台仅用于学习和测试,小伙伴们不要动有坏心思...

PHP 环境 搭建教程(php环境搭建apache)

PHP是一种编程语言,很多网站都用PHP语言编写,我们有时候需要测试一个网站,就需要PHP环境才能运行,又要安装Apache、又要安装MySQL……真的非常麻烦。其实我们可以使用PHP集成...

php手把手教你做网站(六)IIS创建站点注意事项

站点名称:随便填写;应用程序池:创建站点的时候会自动生成对应的,默认就好;物理路径:选择站点所在目录;类型:本地一定是http,网上可能会有https;IP地址:默认端口默认,如果是别的端口,网址访问...

分享PHP网站开发过程中的一些经验

现在的网站建设语言有几种,分别是ASP.NET,PHP,JSP这三种最为常见,这三种语言做出来的网站各有各的特点,asp属于最成熟的建站模式,jsp一般使用在大型网站系统上,对于php是目前比较流行的...