记得有一次面试,面试官问我实现的一个栈会不会有内存泄漏问题。我试图寻找可能的问题,但我就是感觉不到这种可能性。出现问题。那时候突然发现内存泄露的问题被我忽略了,因为我用的是java/C#,这些语言都有内存自动回收的机制。我突然发现我对这个问题一无所知。采访中的堆栈如下://你能检查出“内存泄漏”吗?公共类Stack{私有对象[]元素;私有整数大小=0;privatestaticfinalintDEFAULT_INITIAL_CAPACITY=16;publicStack(){elements=newObject[DEFAULT_INITIAL_CAPACITY];}publicvoidpush(Objecte){ensureCapacity();元素[大小++]=e;}publicObjectpop(){if(size==0)thrownewEmptyStackException();返回元素[--大小];}/***保证栈能自动增长,当栈中空间不足时,会自动增长到原来长度的两倍*/privatevoidensureCapacity(){if(elements.length==size)elements=Arrays.copyOf(元素,2*大小+1);}}这个程序不管你怎么测试都没有问题,但确实有可能造成“内存泄漏”。找到pop()函数。在返回语句中,当我们弹出一个元素时,我们只是让栈顶指针(大小)-1。逻辑上,栈中的这个元素已经出栈,没用了。但实际上,弹出的元素仍然存在于elements数组中,它仍然被elements数组引用,GC无法回收引用的对象。也许你期望当整个栈失去引用时(会被GC回收的时候),栈中的elements数组会一起被GC回收。但是在实际使用中,谁又能预料到这个栈能存活多久。为了保险起见,我们需要在弹出一个元素的时候让这个元素失去引用,这样便于GC回收。我们只需要让Pop()函数弹出并同时取消引用弹出元素即可。publicObjectpop(){if(size==0)thrownewEmptyStackException();对象结果=元素[--s??ize];元素[大小]=null;//消除过期引用returnresult;}从上面的例子我们可以发现,当类持有过期元素的引用时,可能会导致内存泄漏。而通常这种内存泄漏问题都是我们不自觉造成的。在上面的栈中,逻辑上我们认为弹出的元素应该被GC回收,但实际上GC是没有办法回收的,所以elements数组仍然持有。这种问题非常隐蔽。通常只要类自己管理内存(比如类中的Array或List结构),那么我们就应该警惕内存泄漏的问题。内存泄漏的来源及解决方案内存泄漏可能来自缓存。为了让下一个程序的处理速度更快,我们往往需要在内存中缓存一些信息,但是这些过期的缓存很容易被遗忘,以至于在不再有用之后会长期留在缓存中。比如像一个显示图片墙的程序,我们需要缓存图片和相关信息。为了方便GC回收过期的缓存,我们可以使用WeakHashMap来实现缓存。当界面显示图片时,界面持有相关图片的引用。这些引用也存在于WeakHashMap中。对于其他没有被接口持有的过期缓存,WeakHashMap会自动移除。一般来说,WeakHashMap可以用来表示一个缓存,只要在缓存外有一个item的key的引用,并且这个item是有意义的;当缓存中的项目过期时,它们将被自动删除。请记住,WeakHashMap仅在所有缓存项的生命周期由对键的外部引用而非值决定时才有用。更常见的场景是“一个缓存项的生命周期是否有意义”不是很容易确定,而且随着时间的推移,里面的项会越来越没有价值。在这种情况下,应该清除缓存而不丢失项目。这种清理可以由后台线程(可能是Timer或ScheduledThreadPoolExecutor)完成,也可以在新条目添加到缓存时进行清理。LinkedHashMap类可以使用其removeEldestEntry方法轻松实现后一种解决方案。对于更复杂的缓存,必须直接使用java.lang.ref。内存泄漏的第三个常见来源是监听器和其他回调。如果您实现一个API,其中客户端注册回调而不显式注销它们,除非您采取一些措施,否则它们将会累积。确保回调立即被垃圾收集的最好方法是只保留对它们的弱引用,例如只将它们作为键保存在WeakHashMap中。部分文字直接摘自《Effective Java》
