前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >译文:理解Java中的弱引用

译文:理解Java中的弱引用

作者头像
技术小黑屋
发布于 2018-09-04 09:18:48
发布于 2018-09-04 09:18:48
7970
举报
文章被收录于专栏:技术小黑屋技术小黑屋

不久之前,我面试了一些求职Java高级开发工程师的应聘者。我常常会面试他们说,“你能给我介绍一些Java中得弱引用吗?”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我就会基本满意了,我并不期待回答是一篇诘究本末的论文描述。

然而事与愿违,我很吃惊的发现,在将近20多个有着平均5年开发经验和高学历背景的应聘者中,居然只有两个人知道弱引用的存在,但是在这两个人之中只有一个人真正了解这方面的知识。在面试过程中,我还尝试提示一些东西,来看看有没有人突然说一声“原来是这个啊”,结果很是让我失望。我开始困惑,为什么这块的知识如此不被重视,毕竟弱引用是一个很有用途的特性,况且这个特性已经在7年前 Java 1.2发布时便引入了。

好吧,这里我不期待你看完本文之后成为一个弱引用方面的专家,但是我认为至少你应该了解什么是弱引用,如何使用它们,并且什么场景使用。既然它们是一些不知名的概念,我简单就着前面的三个问题来说明一下。

强引用(Strong Reference)

强引用就是我们经常使用的引用,其写法如下

1

StringBuffer buffer = new StringBuffer();

上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。是的,就是这个小儿科的操作(请原谅我这样的说法)。强引用最重要的就是它能够让引用变得强(Strong),这就决定了它和垃圾回收器的交互。具体来说,如果一个对象通过一串强引用链接可到达(Strongly reachable),它是不会被回收的。如果你不想让你正在使用的对象被回收,这就正是你所需要的。

但是强引用如此之强

在一个程序里,将一个类设置成不可被扩展是有点不太常见的,当然这个完全可以通过类标记成final实现。或者也可以更加复杂一些,就是通过内部包含了未知数量具体实现的工厂方法返回一个接口(Interface)。举个例子,我们想要使用一个叫做Widget的类,但是这个类不能被继承,所以无法增加新的功能。

但是我们如果想追踪Widget对象的额外信息,我们该怎么办? 假设我们需要记录每个对象的序列号,但是由于Widget类并不包含这个属性,而且也不能扩展导致我们也不能增加这个属性。其实一点问题也没有,HashMap完全可以解决上述的问题。

1

serialNumberMap.put(widget, widgetSerialNumber);

这表面看上去没有问题,但是widget对象的强引用很有可能会引发问题。我们可以确信当一个widget序列号不需要时,我们应该将这个条目从map中移除。如果我们没有移除的话,可能会导致内存泄露,亦或者我们手动移除时删除了我们正在使用的widgets,会导致有效数据的丢失。其实这些问题很类似,这就是没有垃圾回收机制的语言管理内存时常遇到的问题。但是我们不用去担心这个问题,因为我们使用的时具有垃圾回收机制的Java语言。

另一个强引用可能带来的问题就是缓存,尤其是像图片这样的大文件的缓存。假设你有一个程序需要处理用户提供的图片,通常的做法就是做图片数据缓存,因为从磁盘加载图片代价很大,并且同时我们也想避免在内存中同时存在两份一样的图片数据。

缓存被设计的目的就是避免我们去再次加载哪些不需要的文件。你会很快发现在缓存中会一直包含一个到已经指向内存中图片数据的引用。使用强引用会强制图片数据留在内存,这就需要你来决定什么时候图片数据不需要并且手动从缓存中移除,进而可以让垃圾回收器回收。因此你再一次被强制做垃圾回收器该做的工作,并且人为决定是该清理到哪一个对象。

弱引用(Weak Reference)

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下

1

WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);

使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。

解决上述的widget序列数记录的问题,最简单的办法就是使用Java内置的WeakHashMap类。WeakHashMap和HashMap几乎一样,唯一的区别就是它的键(不是值!!!)使用WeakReference引用。当WeakHashMap的键标记为垃圾的时候,这个键对应的条目就会自动被移除。这就避免了上面不需要的Widget对象手动删除的问题。使用WeakHashMap可以很便捷地转为HashMap或者Map。

引用队列(Reference Queue)

一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。

引用队列可以很容易地实现跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。

四种引用

Java中实际上有四种强度不同的引用,从强到弱它们分别是,强引用,软引用,弱引用和虚引用。上面部分介绍了强引用和弱引用,下面介绍剩下的两个,软引用和虚引用。

软引用(Soft Reference)

软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收期回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么这个对象会被垃圾回收器接下来的回收周期销毁。但是如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象。

由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。这样的话,你就可以节省了很多事情,垃圾回收器会关心当前哪种可到达类型以及内存的消耗程度来进行处理。

虚引用 (Phantom Reference)

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

总结

我想看到这里,很多人开始发牢骚了,为什么你要讲一个过去十年的老古董API呢,好吧,以我的经验看,很多的Java程序员并不是很了解这个知识,我认为有一些深入的理解是很必要的,同时我希望大家能从本文中收获一些东西。

原文信息

附注信息

本文涉及到很多概念对于初次接触的人相对比较难以理解,建议结合英文原文进行研究。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
重磅!腾讯云轻量应用服务器免费升配又双叒来了!
首先前往轻量应用服务器控制台查看机型https://console.cloud.tencent.com/lighthouse/instance/
小宇-xiaoyu
2022/03/04
3.4K0
基于声网的音视频SDK和FreeSWITCH开发WebRTC2SIP Gateway 报文设计 (二)
上一篇我们提到,常用的SIP 信令有:1注册、2振铃、3呼叫、4接听、5挂断、6取消
qzlink.com
2020/07/01
1.1K0
利用云函数接收兔小巢的 WebHooks ,并通知到对应邮箱
兔小巢是一款免费的反馈互动社区产品,在网站上插入几行代码即可拥有和腾讯网一样的互动社区,让反馈变得便捷,轻巧。
jwj
2022/03/28
2K0
利用云函数接收兔小巢的 WebHooks ,并通知到对应邮箱
竞技世界面试官:说一下公平锁和非公平锁的区别?
上次我们提到了乐观锁和悲观锁,那我们知道锁的类型还有很多种,我们今天简单聊一下,公平锁和非公平锁两口子,以及他们在我们代码中的实践。
Java程序猿
2021/07/18
3970
protobuf编译、安装和简单使用C++(Windows+VS平台)
将刚才编译后的libprotobufd.lib和protoc.exe拷贝到自己创建的项目下,按住shift+右键,选择打开CMD,输入protoc --cpp_out=./ Account.proto,发现目录中多了两个文件,一个.h头文件一个.cc源文件
WindSun
2020/03/21
9.1K2
若依前后台框架,下载运行,若依系统生成代码
1、前置工具安装 jdk 1.8 redis (缓存数据库) mysql idea (后端开发工具) nodejs (js前端开发平台,本文主要用到包管理工具 npm) vscode (前端开发工具) git (版本管理工具) 下载 [https://gitee.com/y\_project/RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 2、运作方式 [image.png] 3、后台导入,设置 [image.png] [image.png] [image
用户7957495
2021/08/09
1.6K0
若依前后台框架,下载运行,若依系统生成代码
excel 的条件格式(二)
使用条件格式中的数据条可以非常方便地对一定区域内的单元格的数值进行可视化。照着以下步骤执行,便可以添加数据条。
mr.songw
2021/02/04
1.6K0
excel 的条件格式(二)
【日志服务CLS】初识腾讯 CLS 高速检索与 Nginx 预报警服务~
和尚虽然是移动端开发,但对于 Nginx 负载均衡稍稍有些了解,而曾经的毕业论文就是以 Nginx 为研究方向进行的,今天根据 腾讯云+社区 活动,简单尝试一下 Nginx 日志服务;
阿策小和尚
2021/05/18
1.3K1
PHPStudy + VSCode 进行 PHP 断点调试
文件->首选项->设置->扩展->php->validate:executable path
Action
2021/08/12
2.3K0
PHPStudy + VSCode 进行 PHP 断点调试
都2021年了还不会在CentOS7上使用pyenv搭建Django环境吗?
pyenv是一个forked自ruby社区的简单、低调、遵循UNIX哲学的Python环境管理工具, 它可以轻松切换全局解释器版本, 同时结合vitualenv插件可以方便的管理对应的包源.
查理不是猹
2021/12/17
5430
2021年度Leetcode算法类型高频题总结&(附答案解析)
昨晚逛了逛GitHub,无意中看到一位P8大佬的算法刷题笔记,感觉发现了宝藏!有些小伙伴可能已经发现了,但咱这里还是忍不住安利一波,怕有些小伙伴没有看到。
Java程序猿
2021/12/24
9680
2021年度Leetcode算法类型高频题总结&(附答案解析)
使用Xcode配置OpenCV环境
M1芯片的Mac安装opencv需要专门去找对应的版本去下载。这里稍有不慎,坑会比较多。
AlanWalker
2022/03/16
2.4K0
Case 5 一键安装JS SDK 网页版WebRTC 网页 SIP客户端 语音通话,可以做web坐席
服务器环境要求 CentOS 6.9 64bit 下载脚本文件并安装 wget http://voip-sdk.oss-cn-beijing.aliyuncs.com/rtc_sip/install_rtc_sip.sh wget http://voip-sdk.oss-cn-beijing.aliyuncs.com/rtc_sip/python_install.sh #yum -y install dos2unix #dos2unix ./*.sh chmod a+x ./*.sh ./inst
qzlink.com
2020/07/01
1.9K0
windows单机搭建k8s环境——部署ingress
ingress是k8s对外暴露服务的一种方式。当我们使用购买的公有云服务时,平台已经提供部署好的ingress,用户只需要添加挂载策略即可。
DifficultWork
2021/04/06
1.7K0
【技术创作101训练营】- 前后端分离模式下如何保证开发人员不打架?
上传ppt很多颜色被改变了,如果评委老师想看原版的话,可以看pdf,麻烦了,谢谢。
手撕代码八百里
2021/01/20
1.1K3
【技术创作101训练营】- 前后端分离模式下如何保证开发人员不打架?
【玩转腾讯云】导入镜像-Windows 2008 R2 Datacenter
【腾讯云】关于Windows Server 2008 R2 企业版 SP1 64位公共镜像下线通知
大大大黑白格子
2020/03/26
10.4K18
【玩转腾讯云】导入镜像-Windows 2008 R2 Datacenter
TKE nginx-ingress 开启ip白名单限制和透传client源IP
最近TKE迎来了nginx-ingress 插件的到来,此篇文章将结合TKE nginx-ingress插件,实现IP白名单配置和service透传client源IP的功能
惠绍奇
2020/12/30
4.9K0
Android模仿微博的LazyFragment懒加载
本文会从头开始一步一步带你去写一个LazyFragment,根据写的过程中一步一步记录,你也可以自己试一试,跟着一起写写。最后也根据遇到的问题去完善了,网上搜的都是不完善的,还是自己写一个吧!
分你一些日落
2021/12/08
4900
Android模仿微博的LazyFragment懒加载
【玩转腾讯云】用eclipse跑通腾讯云JavaSDK
https://www.oracle.com/java/technologies/javase-jdk13-downloads.html
大大大黑白格子
2020/03/27
16.9K0
【玩转腾讯云】用eclipse跑通腾讯云JavaSDK
MySQL 索引(中)
聚簇索引是按照每张表的主键构造的一棵 B+ 树,叶子节点中存放的即为整张表的行记录数据,聚簇索引的叶子节点也称为数据页。非聚簇索引叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含一个书签。该书签用来告诉 InnoDB 存储引擎哪里可以找到与索引相对应的行数据。由于 InnoDB 存储引擎表是索引组织表,因此 InnoDB 存储引擎的非聚簇索引的书签就是相应的行数据的聚簇索引键。那么基于聚簇索引和非聚簇索引的查询的区别在哪里呢?先通过一个例子来直观感受下:查询 emp_no 为 401060 的记录,通过字段 emp_no 来查询,sql 如下。
mr.songw
2021/01/15
1.6K0
MySQL 索引(中)
推荐阅读
相关推荐
重磅!腾讯云轻量应用服务器免费升配又双叒来了!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档