本文参考

本篇文章参考自《Effective Java》第三版第七条"Eliminate obsolete object references"

Memory leaks in garbage-collected languages (more properly known as unintentional object retentions) are insidious

在具备垃圾回收器的语言中,内存泄漏(或称无意识对象保留)往往十分隐蔽,看下面一个自定义栈程序的例子

public class Stack {

  private Object[] elements;

  private int size = 0;

  private static final int DEFAULT_INITIAL_CAPACITY = 16;

  public Stack() {

    elements = new Object[DEFAULT_INITIAL_CAPACITY];
  }

  public void push(Object e) {

    ensureCapacity();

    elements[size++] = e;
  }

  public Object pop() {

    if (size == 0) {

      throw new EmptyStackException();
    }

    return elements[--size];
  }

  /**
   * Ensure space for at least one more element, roughly
   * doubling the capacity each time the array needs to grow.
   */

  private void
ensureCapacity() {

    if (elements.length == size) {

      elements = Arrays.copyOf(elements, 2 * size + 1);
    }
  }
}

尽管能够实现我们需要的LIFO的功能,但是在栈指针size先增长再收缩的情况下,栈的内部却始终保留着下标大于size的过期引用(obsolete references),过期引用不会被GC识别并进行回收,而实际上,我们只需要保留下标小于size 的活动部分(active portion)

注意,过期引用和活动部分只是我们自己定义的概念,GC是无法辨认的,只有我们知道过期引用是不重要的部分,所以Stack类的内存也就需要手动进行管理

我们可以看Java自己的Stack类是如何应对垃圾回收的

public synchronized E pop() {

  E obj;

  int len = size();

  obj = peek();

  removeElementAt(len - 1);

  return obj;
}

peek()方法只是读取了栈顶的元素,主要是在removeElementAt()方法

public synchronized void removeElementAt(int index) {

  modCount++;

  if (index >= elementCount) {

    throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
  }

  else if (index < 0) {

    throw new ArrayIndexOutOfBoundsException(index);
  }

  int j = elementCount - index - 1;

  if (j > 0) {

    System.arraycopy(elementData, index + 1, elementData, index, j);
  }

  elementCount--;

  elementData[elementCount] = null; /* to let gc do its work */
}

可以看到最后一行代码elementData[elementCount] = null将栈顶的元素设置为null,这样就能清空过期引用,让GC自动清理"堆"上的内存空间

Nulling out object references should be the exception rather than the norm

清空对象引用应该是一种例外而不是规范,因为在程序运行到超过某些引用的作用域(或生命周期)后,引用会被自动清除

The best way to eliminate an obsolete reference is to let the variable that contained the reference fall out of scope. This occurs naturally if you define each variable in the narrowest possible scope

上述的自定义栈就是一种例外,需要Stack类自己管理内存

Another common source of memory leaks is caches

为了防止我们遗忘缓存中的引用的清理,第一种解决方案是使用WeakHashMap,WeakHashMap含有一个继承了WeakReference弱引用类的Entry静态内部类,当某个键不再被正常使用时,该键会从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map

注意,只被弱引用指向的对象只能存活到下一次 JVM 执行垃圾回收动作之前,即JVM的每一次垃圾回收动作都会回收那些只被弱引用指向的对象

因此只要在缓存之外存在对某个项的键的引用(如强引用和软引用),该项就有意义,那么就可以用 WeakHashMap 代表缓存,当缓存中的项不再被引用(或称过期)之后,它们就会自动被GC删除

有关WeakHashMap的介绍可以参考这篇博文:https://blog.csdn.net/u014294681/article/details/86522487

另一种解决方案是使用LinkedHashMap,他的removeEldestEntry()方法会在插入新映射时被调用,用来移除旧的映射

This method is invoked by put and putAll after inserting a new entry into the map. It provides the implementor with the opportunity to remove the eldest entry each time a new one is added. This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.

A third common source of memory leaks is listeners and other callbacks

如果你实现了一个 API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非你采取某些动作,否则它们就会不断地堆积起来。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将它们保存成 WeakHashMap 中的键

下面代码参考自stack overflow上的回答:https://stackoverflow.com/questions/2859464/how-to-avoid-memory-leaks-in-callback

public interface ChangeHandler {

  void handleChange();
}

public class FileMonitor {

  private File file;

  private Set<ChangeHandler> handlers = new HashSet<ChangeHandler>();
  // private WeakHashMap<ChangeHandler, ?> weakHandler = new WeakHashMap<>();

  public
FileMonitor(File file) {

    this.file = file;
  }

  public void registerChangeHandler(ChangeHandler handler) {

    this.handlers.add(handler);
  }

  public void unregisterChangeHandler(ChangeHandler handler) {

    this.handlers.remove(handler);
  }
}

public class MyClass {

  File myFile = new File("somewhere");

  FileMonitor monitor = new FileMonitor(myFile);

  public void something() {

    // do something ...
    // strong reference declaration
    // the reference will be expired after the scope of something() method ended

    ChangeHandler
myHandler = getChangeHandler();

    monitor.registerChangeHandler(myHandler);

    // if MyClass forgets to call unregisterChangeHandler() when it's done with the handler,
    // the FileMonitor's HashSet will forever reference the instance that was registered,
    // causing it to remain in memory until the FileMonitor is destroyed or the application quits.
    // do something ...

  }

  private ChangeHandler getChangeHandler() {

    return new ChangeHandler() {

      @Override

      public void handleChange() {

        // do something ...

      }
    };
  }
}

Effective Java —— 消除过期的对象引用的更多相关文章

  1. Java 消除过期的对象引用

    内存泄漏的第一个常见来源是存在过期引用. import java.util.Arrays; import java.util.EmptyStackException; public class Sta ...

  2. Effective Java 第三版——7. 消除过期的对象引用

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. 《Effective Java》 读书笔记(七)消除过期的对象引用

    大概看了一遍这个小节,其实这种感觉体验最多的应该是C/C++程序,有多杀少个new就得有多个delete. 一直以为Java就不会存在这个问题,看来是我太年轻. 感觉<Effective Jav ...

  4. Effective Java (6) - 消除过期的对象引用

    一.引言 很多人可能在想这么一个问题:Java有垃圾回收机制,那么还存在内存泄露吗?答案是肯定的,所谓的垃圾回收GC会自动管理内存的回收,而不需要程序员每次都手动释放内存,但是如果存在大量的临时对象在 ...

  5. Effective Java 之-----消除过期的对象引用

    public class Stack { private Object[] elements; private int size = 0; private static final int DEFAU ...

  6. Item 6 消除过期的对象引用

    过期对象引用没有清理掉,会导致内存泄漏.对于没有用到的对象引用,可以置空,这是一种做法.而最好的做法是,把保存对象引用的变量清理掉,多用局部变量.   什么是内存泄漏? 在Java中,对象的内存空间回 ...

  7. 《Effective java》-----读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己!预计在2016年要看12本书,主要涉及java基础.Spring研究.java并 ...

  8. Effective Java笔记一 创建和销毁对象

    Effective Java笔记一 创建和销毁对象 第1条 考虑用静态工厂方法代替构造器 第2条 遇到多个构造器参数时要考虑用构建器 第3条 用私有构造器或者枚举类型强化Singleton属性 第4条 ...

  9. Effective java读书笔记

    2015年进步很小,看的书也不是很多,感觉自己都要废了,2016是沉淀的一年,在这一年中要不断学习.看书,努力提升自己 计在16年要看12本书,主要涉及java基础.Spring研究.java并发.J ...

随机推荐

  1. 为什么用Python,高级的Python是一种高级编程语言

    Python特性 如果有人问我Python最大的特点是什么,我会毫不犹豫地告诉他:它简单易学,功能强大.作为一个纯自由软件,Python有许多优点: 很简单.基于"优雅".&quo ...

  2. 【windows 操作系统】【CPU】用户模式和内核模式(用户层和内核层)

    所有的现代操作系统中,CPU是在两种不同的模式下运行的: 注意以下内容来自微软: windows用户模式和内核模式 运行 Windows 的计算机中的处理器有两个不同模式:用户模式 和内核模式 . 用 ...

  3. 设计模式学习笔记(详细) - 七大原则、UML类图、23种设计模式

    目录 设计模式七大原则 UML类图 设计模式分类 单例模式 工厂设计模式 简单工厂模式 工厂方法模式(使用抽象类,多个is-a) 抽象工厂模式(使用接口,多个like-a) 原型模式 建造者模式 适配 ...

  4. Java常用--反射

    反射的意义 你可能说,平时都是业务的增删查改基本用不到反射.但是如果你学会用反射了,可以减少重复代码,非常的好用. 反射是Java语言的一大特性,允许动态的修改程序行为. 代码说反射 1.反射的三个入 ...

  5. mysql-索引对性能影响

    1.添加索引后查询速度会变快 mysql中索引是存储引擎层面用于快速查询找到记录的一种数据结构,索引对性能的影响非常重要,特别是表中数据量很大的时候,正确的索引会极大的提高查询效率.简单理解索引,就相 ...

  6. WPF 文本描边+外发光效果实现

    解决思路: (1)描边效果可以将文本字符串用GDI+生成Bitmap,然后转成BitmapImage,再用WPF的Image控件显示. (2)外发光效果用WPF自带的Effect实现 代码: 1 us ...

  7. 如何使用Google Analytics Universal Analytics增强型电子商务

    Google Analytics: Universal Analytics增强型电子商务,可以让运营人员轻松地跟踪用户在其购物历程中与产品的互动,包括产品展示.产品点击.查看产品详情.将产品添加到购物 ...

  8. pycharm远程调试、开发(详细操作)

    如果仅是远程开发,新建 ssh Interpreter 并 apply tools -> deployment -> browser remote host 即可 1.服务器侧准备 准备调 ...

  9. 亚马逊云储存器S3 BCUKET安全性学习笔记

    亚马逊云储存器S3 BCUKET安全性学习笔记 Bugs_Bunny CTF – Walk walk CTF 昨天玩了会这个比赛,碰到这题是知识盲点,来记录一下. 先从题目看起吧. http://ww ...

  10. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...