1. 例子代码:
不严格的讲,这段程序有一个“内存泄漏”,随着垃圾回收器活动的增加,或者由于内存占用的不断鞥见,程序性能的降低会逐渐表现出来。在极端情况下,这种内存泄漏会导致磁盘交换,甚至导致程序失败(OutOfMemoryError错误)。
程序中哪里发生了内存泄漏呢?如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为栈内部维护着对这些对象的过期引用。所谓的国企引用,就是指永远也不会被解除的引用。在本例中,凡是在 elements 数组的“活动部分”之外的任何引用都是过期的。活动部分是指 elements 中下标小于size的那些元素。
在支持垃圾回收的语言中,内存泄漏是很隐蔽的(称这类内存泄漏为"无意识的对象保持")。如果一个对象引用被无意识的保留起来了,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象。即使只有少量的几个对象引用被无意识的保留下来,也会有许多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
这类问题的修复方法很简单:一旦对象引用已经过期,只需清空这些引用即可。pop方法修订版:
清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
Stack 类易于遭受内存泄漏影响的原因是:Stack 类自己管理内存(类能够管理内存说的是这个类能够根据自己的状态改变自身的内存的大小(这只是其中的一点) 这个栈类当容量满时就会自动递增一倍空间)。存储池包含了 elements 数组(对象引用单元,而不是对象本身)的元素。数组活动区域中的元素是已分配的,而数组其余部分的元素则是自由的。所以,一旦数组元素变成了非活动部分的一部分,程序员手工清除这些数组元素,就可以避免内存泄漏的问题。
一般来说,只要类时自己管理内存,程序员就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
2. 内存泄漏的另一个常见来源是缓存。一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。解决方法一:只要在缓存之外存在对某个项的键的引用,该项就有意义,可以用 WeakHashMap 代表缓存; 当缓存中的项过期之后,他们就会自动被删除。记住:只有当所要的缓存项的生命周期是由该键的外部引用而不是值决定时,WeakHashMap才有用处。解决方法二:随着时间的推移,缓存中的项会变得越来越没有价值。在这种情况下,缓存应该时不时地清除掉没用的项。这项清除工作可以由一个后台线程(可能是ScheduledThreadPolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便清理缓存(比如:可以用 LinkedHashMap 类的 removeEldestEntry方法实现)。对于更加复杂的缓存,必须直接使用 java.lang.ref。
3. 内存泄漏的第三个常见来源是监听器和其他回调。如果你实现了一个API,客户端在这个API中注册回调,却没有显示地取消注册,那么除非你采取某些动作,否则他们就会不断的堆积起来。确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将他们保存成 WeakHashMap中的键。