zoukankan      html  css  js  c++  java
  • Java性能优化(5):消除过期的对象引用

    当你从一种手工管理内存的语言(比如C/C++)转换到一种具有垃圾回收功能的语言的时候,作为一个苦逼的程序猿,工作这时候变得更加容易。因为当你用完了对象后,它们会自动被回收(现实中的对象可不能这样啊(ฅ>ω<*ฅ))。当你第一次经历对象回收功能的时候,你回觉得这样有点不可思议。这很容易会让你留下这样的印象,认为自己再需要考虑内存管理的事情。然而,其实你想多了。
    不信来看下面简单的栈实现例子:

    public class Stack {
    
        private Object[] elements;
    
        private int size = 0;
    
        public Stack(int initialCapacity) {
            this.elements = new Object[initialCapacity];
        }
    
        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }
    
        private void ensureCapacity() {
            if (elements.length == size) {
                Object[] oldElements = elements;
                elements = new Object[2 * elements.length + 1];
                System.arraycopy(oldElements, 0, elements, 0, size);
            }
    
        }
    }

    在这个程序中没有很显然的错误。无论你如何测试,它都会成功地通过你的每一项测试,但是,这个程序中潜伏着一个问题。那就是这个程序有一个内存泄露,随着垃圾回收器活动的增加,或者由于不断的增加内存占用,程序性能的会逐渐表现出来,在极端情况下,这样的内存泄露会导致磁盘分页,甚至导致程序失败。但是,这样的失败情形相对比较少见。
    So,程序中哪里会发生内存泄露呢?如果一个栈先是增长,然后收缩,那么,从栈中弹出来的对象会不会被当做垃圾回收,即使使用栈的客户程序不再引用这些对象,它们也不会被回收。这是因为,栈内部维护着这些对象的过期引用(永远再也不会被解除的引用)。在例子中,凡是在elements数组的“活动区域”之外的引用都是过期的,elements的活动区域是指下标小于size的那一部分。
    在支持垃圾回收的语言中,内存泄露是很隐蔽。如果一个对象引用被无意识的保留起来了,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象引用的所有其他对象。即使只有少量的几个对象引用被无意识的保留下来,也会有很多对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
    要想修复这一类问题也很简单,一旦对象引用已经过期,只需清空这些引用即可。在上述的stack类中,只要一个单元被弹出栈,指向它的引用就过期了。pop方法的修订版本如下所示:

    public Object pop() {
            if (size == 0) {
                throw new EmptyStackException();
            }
            Object result = elements[--size];
            elements[size] = null;
            return result;
        }

    清空过期引用的另一好处是,如果它们在以后又被错误地解除引用,则程序会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。尽可能早地检测出程序中的错误总是有益的。
    当程序猿第一次被类似这样的问题困扰的时候,他们往往会过分小心:对于每一个对象引用,一旦程序不再用到它,就把它清空。这样做既没必要,也不是我们所期望的,因为这样做会把程序代码弄得很乱,并且可以想象还会降低程序的性能。“清空对象引用”这样的操作应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是重用一个本来已经包含对象引用的变量,或者让这个变量结束其生命周期。如果你是在最紧凑的作用域范围内定义每一个变量,则这种情形就会自然而然地发生。应该注意到,在目前的JVM实现平台上,仅仅退出定义变量的代码块是不够的,要想使引用消失,必须退出包含变量的方法。
    那么,何时应该清空一个引用呢?Stack类的哪方面特性使得它遭受了内存泄露的影响?简而言之,问题在于,Stack类自己管理内存,存储池包含了elements数组(对象引用单元,而不是对象本身)的元素。数组的活动区域中的元素是已分配的,而数组其余部分的元素是自由的。但是GC并不知道这一点:对于垃圾回收器而言,elements数组中的所有对象引用都同等有效。只有程序员知道数组的非活动区域是不重要的,程序员可以把这个情况告知垃圾回收器,做法很简单:一旦数组元素变成了非活动区域的一部分,程序员就手工清空了这些元素。
    一般而言,只要一个类自己管理它的内存,程序员就应该警惕内存泄露问题,一旦一个元素被释放掉,则该元素中包含的任何对象引用应该都要被清空。
    内存泄露的另一个常见来源是缓存。一旦你把一个对象引用放到一个缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间仍然留在缓存中。对于这个问题,有两种可能的解决方案。如果你正巧要实现这样的缓存:只要在缓存之外存在对某个条目的键的引用,该条目就有意义

  • 相关阅读:
    常用CSS书写技巧(不同浏览器之间的差异)
    有关页面滚动条的总结
    SQL数据库字段数据类型说明(整理)
    一个很好玩的程序(有点变态哦~)程序员智商测试!
    自学it18大数据笔记第二阶段Hiveday2——会持续更新……
    自学it18大数据笔记第二阶段avroday1——会持续更新……
    自学it18大数据笔记第二阶段hadoopday8——会持续更新……
    自学it18大数据笔记第一阶段Linuxday1——会持续更新……
    自学it18大数据笔记第二阶段hadoopday5——会持续更新……
    自学it18大数据笔记第一阶段Javaday05day06day07day08
  • 原文地址:https://www.cnblogs.com/ainima/p/6331845.html
Copyright © 2011-2022 走看看