如何在Java中创建内存泄漏?

3720

我最近参加了一次面试,其中被要求使用Java创建一个内存泄漏

不用说,我感到很笨,因为我不知道如何开始创建。

你可以给一个例子吗?


48
具有讽刺意味的是,每个非平凡的Java程序所面临的更难的问题是如何创建内存泄漏! - Peter - Reinstate Monica
3
不断往容器中添加新对象,却忘记添加删除它们的代码,或者实现部分工作的代码无法在程序运行时清理所有对象。 - Galik
5
Java服务器系统中最常见的内存泄漏是在共享状态下发生的,例如请求之间共享的缓存和服务。许多答案似乎过于复杂,忽略了这个明显而常见的领域。可能存在一种常见的泄漏模式,即具有请求作用域键(例如某种自定义缓存)的应用程序范围Map。 - Thomas W
61个回答

7

7
在Java中如何创建内存泄漏的问题,有很多答案,但请注意面试时的问题。这是一个开放性问题,“如何在Java中创建内存泄漏?”旨在评估开发人员的经验程度。
如果我问你:“你有没有解决Java内存泄漏的经验?”你的回答将是简单的“是”。然后我会跟进:“你能举个例子吗?你必须解决内存泄漏的情况?” 你会给我一两个例子。
然而,当面试官问“如何在Java中创建内存泄漏?”时,预期的答案应沿着以下方向展开:
- 我遇到了内存泄漏...(例如何时),[这显示了我的经验] - 引起内存泄漏的代码是...(解释代码)[你自己修复了] - 我应用的修复程序基于...(解释修复程序)[这给了我询问修复程序的具体细节的机会] - 我进行的测试是... [给我询问其他测试方法的机会] - 我以这种方式记录下来... [额外的分数。如果你记录下来就很好]
所以,可以合理地认为,如果我们按相反的顺序进行操作,即获取我修复的代码并删除我的修复,那么我们将会出现内存泄漏。
当开发人员未能按照这种思路进行时,我会尝试引导他们回答“你可以举个Java如何泄漏内存的例子吗?”然后是“你是否曾经不得不解决Java中的任何内存泄漏?”
请注意,我要求示例如何在Java中泄漏内存。那太愚蠢了。谁会对能够有效地写出泄漏内存的代码的开发人员感兴趣呢?

关于最后一句话,战胜邪恶的最好方法是深入了解它。如果你想编写一个安全的Web应用程序,你应该熟悉最常见的攻击技术和漏洞,比如SQL注入或缓冲区溢出。同样,如果你想编写无泄漏代码,你至少应该能够描述最常见的内存泄漏方式,比如C/C++中的丢失指针。虽然在Java中可能不那么直接。 - Stefano Sanfilippo

7

在finalize方法中抛出未处理的异常。


6
一种可能的方法是创建一个ArrayList的包装器,只提供一种方法:向ArrayList添加元素。将ArrayList本身设置为私有的。现在,在全局范围内构造一个这样的包装器对象(作为类中的静态对象),并用final关键字来限定它(例如public static final ArrayListWrapper wrapperClass = new ArrayListWrapper())。因此,引用不能被改变。也就是说,wrapperClass = null不起作用,不能用于释放内存。但是,除了向其中添加对象之外,没有任何方式可以处理wrapperClass。因此,您向wrapperClass添加的任何对象都无法回收利用。

5

Swing对话框的使用非常简单。创建一个JDialog,展示它,用户关闭它,就完成了!

您需要调用dispose()或配置setDefaultCloseOperation(DISPOSE_ON_CLOSE)


5
在Java中,“内存泄漏”主要是指你使用过多的内存,这与C语言不同,后者虽然不再使用内存但忘记返回(释放)它。当面试官询问Java内存泄漏时,他们是在询问JVM内存使用量似乎不断增加,并确定定期重启JVM是最好的解决方法(除非面试官非常懂技术)。
因此,回答这个问题时,就像他们询问什么会导致JVM内存使用随时间增长一样。良好的答案可能是在HttpSessions中存储过多数据并设置了过长的超时时间,或者是一个糟糕的实现内存缓存(单例),永远不会刷新旧条目。另一个潜在的答案是有大量的JSP或动态生成的类。类被加载到一个称为PermGen的内存区域中,通常很小,大多数JVM都不实现类卸载。

4

在具有自己的生命周期的类中粗心地使用非静态内部类。

在Java中,非静态内部类和匿名类都持有对其外部类的隐式引用。而静态内部类则没有

以下是一个在Android中可能导致内存泄漏的常见示例,尽管不太明显:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { // Non-static inner class, holds the reference to the SampleActivity outer class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //....
      }
    }, SOME_TOME_TIME);

    // Go back to the previous Activity.
    finish();
  }}

这将防止活动上下文被垃圾回收。

mLeakyHandler设置为静态变量可以防止内存泄漏吗?还有没有其他方法可以防止mLeakyHandler导致Activity内存泄漏? 此外,您如何解决匿名的Runnable内部类的相同问题? - ban-geoengineering
1
@ban-geoengineering 是的,请将其设置为静态,如果需要涉及外部活动,请使处理程序持有对活动的弱引用,请参阅http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html。 - JaskeyLam

4

失效的监听器是内存泄漏的一个很好的例子:对象被添加为监听器。当不再需要该对象时,所有对该对象的引用都将被置空。然而,如果忘记从监听器列表中删除该对象,则会使该对象保持活动状态并响应事件,从而浪费内存和CPU资源。请参见http://www.drdobbs.com/jvm/java-qa/184404011


4
如果你不使用紧缩垃圾收集器,由于堆碎片化,可能会导致内存泄漏的情况发生。

3
记忆泄漏是资源泄漏的一种类型,当计算机程序错误地管理内存分配,以至于不再需要的内存没有被释放时发生 => 维基百科定义
这是一种相对上下文相关的主题,只要未使用的引用永远不会被客户端使用,但仍然保持活动状态,您可以根据自己的喜好创建一个主题。
第一个例子应该是一个自定义堆栈,没有在Effective Java, item 6中将过时的引用设为null。
当然,还有很多其他的例子,只要你想要,但如果我们只看Java内置类,可能会有一些,比如: subList() 让我们检查一些非常愚蠢的代码来产生泄漏。
public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

实际上,我们可以利用HashMap的查找过程来造成内存泄漏的另一个技巧。它实际上有两种类型:

  • hashCode()总是相同,但equals()不同;
  • 使用随机hashCode()equals()始终为true;

为什么?

hashCode() -> bucket => equals()用于定位键值对


我本来要先提到 substring(),然后再用 subList(),但好像这个问题在JDK 8中已经修复了,因为它的源代码已经存在。

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接