一个极易被忽略的内存泄漏情况,看看你会不会犯一样的错

简介: Java之所以能够成为世界上最受欢迎的语言,与其垃圾回收机制分不开。我们Javaer能够在创建完对象后就不用管她的生死,确实是十分方便(真特么是个渣男)。可是有时候因为你创建了她,又对她爱答不理,就很有可能出大问题。

Java之所以能够成为世界上最受欢迎的语言,与其垃圾回收机制分不开。我们Javaer能够在创建完对象后就不用管她的生死,确实是十分方便(真特么是个渣男)。可是有时候因为你创建了她,又对她爱答不理,就很有可能出大问题。


强哥在这给你看个代码:


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];    }    private void ensureCapacity() {        if (elements.length == size)            elements = Arrays.copyOf(elements, 2 * size + 1);    }}


一个简单的栈实现,代码规范,符合逻辑,本地测试也测不出什么问题。可是当你将这个栈类用到生产环境中,在一段时间过后,你很有可能就会发现随着垃圾回收活动不断增加或者内存占用不断增加,程序会开始越来越卡,而且垃圾回收慢慢的不管用了。


没错,上面的代码隐藏着一个内存泄漏的问题。


在极端情况下,这种内存泄漏可能导致磁盘分页,甚至出现 OutOfMemoryError 程序故障。


那么内存泄漏在哪里呢?罪魁祸首就是在pop方法中:如果堆栈增长,然后收缩,虽然减小了size,但是数组的总体长度是不会减小的,而被弹出的那个对象我们如果使用后没有手动做清除处理的话,由于栈中的elements数组还保持着对该对象的引用,那么弹出的对象即使在使用完后也不会被垃圾回收器收集。


这一点确实非常容易被人所忽略,但是如果发现了,要处理起来也很简单,方法如下:


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


没错,在pop将数组中将弹出位的引用设置成null就可以了。这样用完该弹出对象后,垃圾回收机制就能够发现该对象并将其回收。


由于问题隐蔽,且对性能影响比较大。所以发现这样的问题之后,我们往往心里就会很担心这种情况的再次发生,然后做出每一个对象,一旦程序不使用了,就手动将该对象设置成null。额,强哥只想说,这样的话,那Java语言的优势就彻底没有了,而且代码会很难看。Effective Java中便提到:清空对象引用应该是一种例外,而不是一种规范行为。


消除过时引用的最佳方法是让包含引用的变量脱离作用域。如果你在最狭窄的范围内定义每个变量,那么这种情况自然会发生。


那么,什么时候应该取消引用呢?Stack 类的哪些方面容易导致内存泄漏?简单地说,它管理自己的内存。elements存储池包含的元素是对象引用,而不是对象本身。数组的活动部分(小于size的部分)中的元素被分配,而数组其余部分中的元素是空闲的。但是垃圾收集器没有办法知道这一点;对于垃圾收集器,元素数组中的所有对象引用都同样有效。只有我们自己知道数组的非活动部分不重要。只要数组元素成为非活动部分的一部分,就可以通过手动清空数组元素,有效地将这个事实传递给垃圾收集器。


这就像你去图书馆里借书,图书管系统记录了书被你借走了,标记这本书已被借出。但是当你还回去的时候,图书管的人员没有把这个这本书的状态改成已归还,可以外借的时候,其他人就没办法再借到这本书了。如果这种情况一直都存在的话,很久以后,这个图书馆的书就可能都借不了了,也就是我们所谓的内存泄漏。


所以,一般来说,当类管理自己的内存时,咱们就应该警惕内存泄漏。每当释放一个元素时,元素中包含的任何对象引用都应该被取消。

相关文章
|
Apache 云计算 开发者
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(1)
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!
164 0
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(1)
|
安全 小程序 程序员
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(2)
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!
110 0
删库跑路大神「后悔」了?我只不过犯了大家都会犯的编程错误!(2)
|
编译器 C语言
《C陷阱与缺陷》之“语义”陷阱——数组越界导致的程序死循环问题
《C陷阱与缺陷》之“语义”陷阱——数组越界导致的程序死循环问题
149 0
|
Java 编译器 C++
JVM优化过头了,直接把异常信息优化没了? (下)
JVM优化过头了,直接把异常信息优化没了? (下)
151 0
JVM优化过头了,直接把异常信息优化没了? (下)
|
Java 编译器
JVM优化过头了,直接把异常信息优化没了? (上)
JVM优化过头了,直接把异常信息优化没了? (上)
129 0
JVM优化过头了,直接把异常信息优化没了? (上)
|
消息中间件 缓存 Java
JVM优化过头了,直接把异常信息优化没了? (中)
JVM优化过头了,直接把异常信息优化没了? (中)
189 0
JVM优化过头了,直接把异常信息优化没了? (中)
|
算法 Java 编译器
Java开发最常犯的10个错误,打死都不要犯!
阅读目录 Array转ArrayList 判断一个数组是否包含某个值 在循环内部删除List中的一个元素 HashTable与HashMap 使用集合原始类型(raw type) 访问级别 ArrayList和LinkedList 可变与不可变 父类和子类的构造方法 “”还是构造方法 未来工作
176 0
Java开发最常犯的10个错误,打死都不要犯!
|
数据可视化
7个新手数据讲述者犯下的致命错误,你都知道吗?
  本文约1800字,建议阅读5分钟。   本文介绍了新手数据讲师所犯的最常见的错误,以及如何改正它们。   在制作数据故事时,很容易迷失在细节中,并且无法创造出可以激发别人动手操作的数据故事。下面是新手数据讲师所犯的最常见的错误,以及如何改正它们。这些技巧来自“像数据讲述者一样思考”研究会。   1. 数据故事不适合听众   不是所有的听众都是相同的,不是所有的听众都有一样的目标。即使您自己审视自己团队内部,也可以考虑一个技术支持专家和一个运营主管如何具有不同的观点。尽管两者都有共同的目标即服务客户,但每个人对于这个目标能够实现的方法和原因具有不同的观点。   许多展示数据故事的讲
137 0
|
Python 索引
初学Python常见异常错误,总有一处你会遇到!
初学Python常见错误 忘记写冒号 误用= 错误 缩紧 变量没有定义 中英文输入法导致的错误 不同数据类型的拼接 索引位置问题 使用字典中不存在的键 忘了括号 漏传参数 缺失依赖库 使用了python中对关键词 编码问题 1.
1601 0