美文网首页Effective Java
第6条 消除过期的对象引用

第6条 消除过期的对象引用

作者: 郭_4d5f | 来源:发表于2017-05-01 11:51 被阅读0次
    图1

    如图所示的例子,这段程序中没有明显的错误,但是存在一个隐藏的问题(“内存泄漏”),随着垃圾回收活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来,在极端的情况下,这种内存泄漏会导致磁盘交换,甚至导致程序失败。

    如果栈先是增长,然后在收缩,那么从栈中弹出的对象不会被当做垃圾回收,即使使用栈的程序不在引用这些对象,他们也不会被回收。因为,栈内部维护着对这些对象的过期引用。过期引用是指永远也不会解除的引用。在以上这个例子中,elements数组的活动部分之外的所有引用都是过期的。活动部分是指elements数组中下标小于size的部分。

    如果一个对象引用被无意识的保留下来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理处理这个对象所引用的所有其他对象。即使少量的几个对象被无意识的保留下来,也许会有许多对象被排除在垃圾回收之外,并由此对性能造成重大影响。

    解决方法:一旦引用对象已经过期,清空这些引用。

    例子中的statck类,只要一个单元被弹出,指向他的引用就过期了,pop方法修改为

    public object pop(){

    if(size ==0){

    throw new EmptyStackException();

    }

    ObJect result = elements[--size]//--放在前面为先减一再使用

    element[size] = null;

    return result;

    }

    清空过期引用的另外一个好处是,如果他们以后又被错误的解除引用,程序会立即抛出空指针异常,而不是悄悄的错误的运行下去。

    清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用的最好的方法是让包含该引用的变量结束其生命周期。如果在最紧凑的作用域范围内定义一个变量

    如下例 1

    Iterator iter = l.iterator();

    while(iter.hasNext()){

    String str = (String) iter.next();

    System.out.println(str);

    }

    例2

    while(Iterator iter = l.iterator();iter.hasNext()){

    String str = (String) iter.next();

    System.out.println(str);

    }

    例2这种就比较合适,iter的生命周期循环结束后结束

    stack类自己管理内存,存储池包含了elements数组的元素,数组活动区域中的元素是已分配的,而数组其余部分的元素是自由的,但是垃圾回收器并不知道这一点,对于垃圾回收来说,elements数组中的所有对象引用都同等有效。只有我们本身知道数组的非活动部分是不重要的,所以需要我们手动清空这些元素。

    一般而言,只要是自己管理内存,就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应被清空掉。

    内存泄漏的另一个常见的来源是缓存,一旦把对象引用放入缓存中,它就很容易被遗忘。对于这种情况有几种可能的解决方案,如果你正好要实现一个只要在缓存之外存在对某个项的键的引用,该项就有意义这样的缓存的话,就可以使用WeakHashMap代表缓存,因为当缓存中的项过期的时候,它们就会自动被删除掉。但是只有当所要的缓存项的生命周期是由key的外部引用而不是由value决定时WeakHashMap才有用处。

    更常见的情况是“缓存项的生命周期是否有意义”,并不是很容易确定,随着时间的推移,其中的项会变得越来越没有价值,在这种情况下,缓存应该时不时地清除掉没用的项,这种工作可以交给一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便进行。LinkedHashMap类利用它的removeEldestEntry方法可以很容易的实现后一种方案,对于更加复杂的缓存,就只能直接使用java.lang.ref了。

    内存泄漏的第三种常见来源是监听器和其他的回调函数。如果客户在你实现的API中注册回调,但是却没有显示取消注册。那么除非你采取些手段,否则它们就会积聚。确保回调立即被当作垃圾的最佳方法是只保存它们的弱引用,例如,只将他们保存为WeakHashMap中的键。

    WeakHashmap 的简单介绍

    1. 以弱键 实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除

    2. WeakHashMap 类的行为部分取决于垃圾回收器的动作。因为垃圾回收器在任何时候都可能丢弃键,WeakHashMap 就像是一个被悄悄移除条目的未知线程。特别地,即使对 WeakHashMap 实例进行同步,并且没有调用任何赋值方法,在一段时间后 size 方法也可能返回较小的值,对于 isEmpty 方法,返回 false,然后返回true,对于给定的键,containsKey 方法返回 true 然后返回 false,对于给定的键,get 方法返回一个值,但接着返回 null,对于以前出现在映射中的键,put 方法返回 null,而 remove 方法返回 false,对于键 set、值 collection 和条目 set 进行的检查,生成的元素数量越来越少。

    3. WeakHashMap 中的每个键对象间接地存储为一个弱引用的指示对象。因此,不管是在映射内还是在映射之外,只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。

    例3

    BufferedReader br=newBufferedReader(newFileReader(file));//构造一个BufferedReader类来读取文件

    String s =null;

    String s1 =""

    while((s = br.readLine())!=null){//使用readLine方法,一次读一行

    s1 = s1+s

    result.append(System.lineSeparator()+s);

    }

    此例是曾经写过的一个列子,用s用来读取文件中的行的内容,用s1进行接收,当文件中的内容过多时,会导致内存溢出

    PS:补充一下堆栈的基础知识。

    栈(stack),有时候我们也称为堆栈(这一点可能会让很多小伙伴迷茫)。是由操作系统自动分配和释放的,用来存放局部变量,基本类型的值(比如int,char,boolean等),因为它是操作系统自动分配和释放的,所以通常我们看不到栈的操作。另外,栈是先进先出的。

    堆(heap)。由程序员自己来分配和释放。用来存放用new创建的对象和数组。注意,前面我们说了“由程序员自己来分配和释放”,实际上在Java里面,是由程序员自己来分配的(new),但是不是由程序员自己来释放的,而是通过GC(垃圾回收器)来完成释放的,程序员完全感知不到。

    方法区(method)。又叫静态区,用来存放在整个程序中都是唯一的元素,比如所有的class,以及static变量

    相关文章

      网友评论

        本文标题:第6条 消除过期的对象引用

        本文链接:https://www.haomeiwen.com/subject/ausftxtx.html