Java基础 之软引用、弱引用、虚引用,阿里腾讯PDD等大厂Android面试真题

文章探讨了Java中弱引用和软引用在内存管理中的应用,特别是在构建缓存系统时如何避免内存泄漏,以及如何通过强引用和弱引用的组合优化对象的生命周期管理。
摘要由CSDN通过智能技术生成
  1. public boolean equals(Object r) {

  2. return (r instanceof Key) && id.equals(((Key) r).id);

  3. }

  4. public void finalize() {

  5. System.out.println("Finalizing Key " + id);

  6. }

  7. }

  8. class Value {

  9. String id;

  10. public Value(String id) {

  11. this.id = id;

  12. }

  13. public String toString() {

  14. return id;

  15. }

  16. public void finalize() {

  17. System.out.println("Finalizing Value " + id);

  18. }

  19. }

  20. public class MapCache {

  21. public static void main(String[] args) throws Exception {

  22. int size = 1000;// 或者从命令行获得size的大小

  23. if (args.length > 0)

  24. size = Integer.parseInt(args[0]);

  25. Key[] keys = new Key[size]; // 存放键对象的强引用

  26. WeakHashMap whm = new WeakHashMap();

  27. for (int i = 0; i < size; i++) {

  28. Key k = new Key(Integer.toString(i));

  29. Value v = new Value(Integer.toString(i));

  30. if (i % 3 == 0)

  31. keys[i] = k; // 使Key对象持有强引用 

  32. whm.put(k, v); // 使Key对象持有弱引用

  33. }

  34. // 催促垃圾回收器工作

  35. System.gc();// 把CPU让给垃圾回收器线程

  36. Thread.sleep(8000);

  37. }

  38. }

程序输出结果:

java代码:

查看 复制到剪贴板 打印

  1. Finalizing Key 137

  2. Finalizing Key 368

  3. Finalizing Key 415

  4. Finalizing Key 470

  5. Finalizing Key 514

  6. Finalizing Key 557

  7. Finalizing Key 601

  8. Finalizing Key 643

  9. Finalizing Key 686

  10. Finalizing Key 730

  11. Finalizing Key 775

  12. Finalizing Key 817

  13. Finalizing Key 862

  14. Finalizing Key 907

  15. Finalizing Key 952

  16. Finalizing Key 995

  17. Finalizing Key 998

  18. Finalizing Key 997

从打印结果可以看出,当执行System.gc()方法后,垃圾回收器只会回收那些仅仅持有弱引用的Key对象。id可以被3整数的Key对象持有强引用,因此不会被回收。

7、使用软引用构建敏感数据的缓存

7.1 为什么需要使用软引用

首先,我们看一个雇员信息查询系统的实例。我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。作为一 个用户,我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退”按钮)。这时我们 通常会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另一种 是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当 用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程 还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。我们知道,访问磁盘文件、访问网络资源、查询数据库 等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。

7.2 如果使用软引用

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

看下面代码:

java代码:

查看 复制到剪贴板 打印

  1. MyObject aRef = new MyObject();

  2. SoftReference aSoftRef=new SoftReference(aRef);

此时,对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,所以这个MyObject对象是强可及对象。

随即,我们可以结束aReference对这个MyObject实例的强引用:

aRef = null;

此后,这个MyObject对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终 保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存 需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软 可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们可以通过:

MyObject anotherRef=(MyObject)aSoftRef.get();

重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。

7.3 使用ReferenceQueue清除失去了软引用对象的SoftReference

作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后, 虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清 除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建 SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:

java代码:

查看 复制到剪贴板 打印

  1. ReferenceQueue queue = new ReferenceQueue();

  2. SoftReference ref=new SoftReference(aMyObject, queue);

那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入 ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的 Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中 不是空队列,那么将返回队列前面的那个Reference对象。

在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个 null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回 收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。常用的方式为:

java代码:

查看 复制到剪贴板 打印

  1. SoftReference ref = null;

  2. while ((ref = (EmployeeRef) q.poll()) != null) {

  3. // 清除ref

  4. }

理解了ReferenceQueue的工作机制之后,我们就可以开始构造一个Java对象的高速缓存器了。

7.4通过软可及对象重获方法实现Java对象的高速缓存

利用Java2平台垃圾收集机制的特性以及前述的垃圾对象重获方法,我们通过一个雇员信息查询系统的小例子来说明如何构建一种高速缓存器来避免重复构建同一个对象带来的性能损失。我们将一个雇员的档案信息定义为一个Employee类:

java代码:

查看 复制到剪贴板 打印

  1. public class Employee {

  2. private String id;// 雇员的标识号码

  3. private String name;// 雇员姓名

  4. private String department;// 该雇员所在部门

  5. private String Phone;// 该雇员联系电话

  6. private int salary;// 该雇员薪资

  7. private String origin;// 该雇员信息的来源

  8. // 构造方法

  9. public Employee(String id) {

  10. this.id = id;

  11. getDataFromlnfoCenter();

  12. }

  13. // 到数据库中取得雇员信息

  14. private void getDataFromlnfoCenter() {

  15. // 和数据库建立连接井查询该雇员的信息,将查询结果赋值

  16. // 给name,department,plone,salary等变量

  17. // 同时将origin赋值为"From DataBase"

  18. }

  19. ……

这个Employee类的构造方法中我们可以预见,如果每次需要查询一个雇员的信息。哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这是需要消耗很多时间的。下面是一个对Employee对象进行缓存的缓存器的定义:

java代码:

查看 复制到剪贴板 打印

  1. import java.lang.ref.ReferenceQueue;

  2. import java.lang.ref.SoftReference;

  3. import java.util.Hashtable;

  4. public class EmployeeCache {

  5. static private EmployeeCache cache;// 一个Cache实例

  6. private Hashtable employeeRefs;// 用于Cache内容的存储

  7. private ReferenceQueue q;// 垃圾Reference的队列

  8. // 继承SoftReference,使得每一个实例都具有可识别的标识。

  9. // 并且该标识与其在HashMap内的key相同。

  10. private class EmployeeRef extends SoftReference {

  11. private String _key = “”;

  12. public EmployeeRef(Employee em, ReferenceQueue q) {

  13. super(em, q);

  14. _key = em.getID();

  15. }

  16. }

  17. // 构建一个缓存器实例

  18. private EmployeeCache() {

  19. employeeRefs = new Hashtable();

  20. q = new ReferenceQueue();

  21. }

  22. // 取得缓存器实例

  23. public static EmployeeCache getInstance() {

  24. if (cache == null) {

  25. cache = new EmployeeCache();

  26. }

  27. return cache;

  28. }

  29. // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用

  30. private void cacheEmployee(Employee em) {

  31. cleanCache();// 清除垃圾引用

  32. EmployeeRef ref = new EmployeeRef(em, q);

  33. employeeRefs.put(em.getID(), ref);

  34. }

  35. // 依据所指定的ID号,重新获取相应Employee对象的实例

  36. public Employee getEmployee(String ID) {

  37. Employee em = null;

  38. // 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。

  39. if (employeeRefs.containsKey(ID)) {

  40. EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);

  41. em = (Employee) ref.get();

  42. }

  43. // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,

  44. // 并保存对这个新建实例的软引用

  45. if (em == null) {

  46. em = new Employee(ID);

  47. System.out.println(“Retrieve From EmployeeInfoCenter. ID=” + ID);

  48. this.cacheEmployee(em);

  49. }

  50. return em;

  51. }

  52. // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象

  53. private void cleanCache() {

  54. EmployeeRef ref = null;

  55. while ((ref = (EmployeeRef) q.poll()) != null) {

  56. employeeRefs.remove(ref._key);

  57. }

  58. }

  59. // 清除Cache内的全部内容

  60. public void clearCache() {

  61. cleanCache();

  62. employeeRefs.clear();

  63. System.gc();

  64. System.runFinalization();

  65. }

  66. }

8.使用弱引用构建非敏感数据的缓存

8.1全局 Map 造成的内存泄漏

无意识对象保留最常见的原因是使用Map将元数据与临时对象(transient object)相关联。假定一个对象具有中等生命周期,比分配它的那个方法调用的生命周期长,但是比应用程序的生命周期短,如客户机的套接字连接。需要将 一些元数据与这个套接字关联,如生成连接的用户的标识。在创建Socket时是不知道这些信息的,并且不能将数据添加到Socket对象上,因为不能控制 Socket 类或者它的子类。这时,典型的方法就是在一个全局 Map 中存储这些信息,如下面的 SocketManager 类所示:使用一个全局 Map 将元数据关联到一个对象。

java代码:

查看 复制到剪贴板 打印

  1. public class SocketManager {

  2. private Map<SOCKET,USER> m = new HashMap<SOCKET,USER>();

  3. public void setUser(Socket s, User u) {

  4. m.put(s, u);

  5. }

  6. public User getUser(Socket s) {

  7. return m.get(s);

  8. }

  9. public void removeUser(Socket s) {

  10. m.remove(s);

  11. }

  12. }

这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩,但是除非准确地知道什么时候程序不再需要这个套接字,并记住从 Map 中删除相应的映射,否则,Socket 和 User 对象将会永远留在 Map 中,远远超过响应了请求和关闭套接字的时间。这会阻止 Socket 和 User 对象被垃圾收集,即使应用程序不会再使用它们。这些对象留下来不受控制,很容易造成程序在长时间运行后内存爆满。除了最简单的情况,在几乎所有情况下找出 什么时候 Socket 不再被程序使用是一件很烦人和容易出错的任务,需要人工对内存进行管理。

8.2如何使用WeakHashMap

在Java集合中有一种特殊的Map类型—WeakHashMap,在这种Map中存放了键对象的弱引用,当一个键对象被垃圾回收器回收时,那么相应的值 对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。关于Map接口的一般用法。

下面示例中MapCache类的main()方法创建了一个WeakHashMap对象,它存放了一组Key对象的弱引用,此外main()方法还创建了一个数组对象,它存放了部分Key对象的强引用。

java代码:

查看 复制到剪贴板 打印

  1. import java.util.WeakHashMap;

  2. class Element {

  3. private String ident;

  4. public Element(String id) {

  5. ident = id;

  6. }

  7. public String toString() {

  8. return ident;

  9. }

  10. public int hashCode() {

  11. return ident.hashCode();

  12. }

  13. public boolean equals(Object obj) {

  14. return obj instanceof Element && ident.equals(((Element) obj).ident);

  15. }

  16. protected void finalize(){

  17. System.out.println("Finalizing “+getClass().getSimpleName()+” "+ident);

  18. }

  19. }

  20. class Key extends Element{

  21. public Key(String id){

  22. super(id);

  23. }

  24. }

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

愿你有一天,真爱自己,善待自己。

本文在开源项目:Android开发不会这些?如何面试拿高薪 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-qrTZQ0Nv-1711055320335)]
[外链图片转存中…(img-VBcKPSVb-1711055320336)]
[外链图片转存中…(img-kk8mi14z-1711055320336)]
[外链图片转存中…(img-6g9BXgqT-1711055320337)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-COtVYb3a-1711055320337)]

最后

愿你有一天,真爱自己,善待自己。

本文在开源项目:Android开发不会这些?如何面试拿高薪 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值