最简单的方法来导致内存泄漏的Java [重复]

sg2wtvxw  于 2023-02-07  发布在  Java
关注(0)|答案(9)|浏览(168)
    • 此问题在此处已有答案**:

10年前关闭了。

    • 可能重复:**

Creating a memory leak with Java
导致Java内存泄漏的最简单方法是什么?

raogr8fs

raogr8fs1#

在Java中,除非执行以下操作,否则无法真正"泄漏内存":

  • 内部字符串
  • 生成类
  • JNI调用的本机代码中的内存泄漏
  • 把你不想要的东西放在某个被遗忘或模糊的地方。

我想你对最后一种情况感兴趣。常见的情况是:

  • 侦听器,尤其是使用内部类时
  • 缓存。

一个很好的例子是:

  • 构建一个Swing GUI,它可以启动无限数量的模态窗口;
  • 让模态窗口在初始化时做类似这样的事情:`
StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
// do nothing...
}
})

`

注册的动作什么也不做,但它会导致模态窗口永远停留在内存中,甚至在关闭后,从而导致泄漏-因为侦听器从未被注册,每个匿名的内部类对象都拥有一个对外部对象的引用(不可见)。更重要的是-任何从模态窗口引用的对象也有泄漏的机会。
这就是为什么像EventBus这样的库默认使用弱引用的原因。
除了监听器,其他典型的例子是缓存,但我想不出一个好的例子。

t30tvxxf

t30tvxxf2#

首先,我们必须就内存泄漏实际上是什么达成一致。
维基百科used to describe a memory leak像这样:
计算机科学中的内存泄漏(英语:Memory Leak)是指计算机程序消耗内存,但无法将其释放回操作系统。
然而,这已经改变了多次,现在(02/2023)它说:
在计算机科学中,内存泄漏(英语:Memory Leak)是一种资源泄漏,发生在计算机程序错误地管理内存分配,使得不再需要的内存不被释放。
根据上下文,您需要更精确地指定您要查找的内容。

无法访问动态分配的内存

首先,让我们快速浏览一个没有自动内存管理的语言示例:在C语言中,你可以使用malloc()来分配内存。这个函数返回一个指向已分配内存的指针。你必须恰好在这个指针上调用free(),以便将内存释放回操作系统。但是如果这个指针在多个地方使用呢?谁负责调用free()?如果你过早地释放内存,那么你的应用程序的某些部分仍然在使用这个内存就被破坏了。2如果你不释放内存,你就有了一个漏洞。3如果所有指向内存分配的指针都丢失了(覆盖或超过寿命),则应用程序将无法将内存释放回操作系统。这将满足Wikipedia在2011年对内存泄漏的旧定义。为了避免这种情况,您需要某种类型的契约来定义谁负责释放已分配的内存。这需要文档,必须阅读,可能许多人正确理解和遵循,从而产生各种错误机会。
自动内存管理(Java有)把你从这种危险中解脱出来,在Java中你可以使用关键字new分配内存,但是在Java中没有freenew返回一个"引用",其中(在此上下文中)的行为类似于指针。当所有对已分配内存的引用丢失时(覆盖或超过使用寿命),则系统会自动检测到该问题,并将内存返回给操作系统。
在Java中,这种类型的内存泄漏仅在垃圾收集器、JNI模块或类似模块出现错误时才"可用",但至少在理论上是安全的。

其他编程错误

当然,无论是否使用自动内存管理,都可以主动维护不需要的引用。假设下面的类:

class Demo {
    private static final LinkedList<Integer> history = new LinkedList<>(Collections.singleton(0));

    public static int plusPrevious(int value) {
        int result = history.getLast() + value;
        history.add(value);
        return result;
    }
}

每当有人调用plusPrevious时,history-List就会增长。但是为什么呢?只需要一个值,而不是完整的历史记录。这个类占用了它不需要的内存。这满足了Wikipedia对内存泄漏的当前定义。
在这种情况下,错误是显而易见的。然而,在更复杂的场景中,决定什么仍然"需要",什么不是那么容易。
无论如何,将内容放入static变量是陷入麻烦的"好"开始。如果在上面的例子中history不是static,那么该类的用户可能最终释放对Demo示例的引用,从而释放内存。然而,由于它是静态的,历史将一直挂起,直到应用程序作为一个整体终止。

wbgh16ku

wbgh16ku3#

这里有一个简单的例子

public class Finalizer {
    @Override
    protected void finalize() throws Throwable {
        while (true) {
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 100000; i++) {
                Finalizer f = new Finalizer();
            }

            System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!");
        }
    }
}
c90pui9n

c90pui9n4#

use:

public static List<byte[]> list = new ArrayList<byte[]>();

然后添加(大的)数组而不删除它们。在某个时候,你会毫无疑问地耗尽内存。(你可以对任何对象这样做,但是对于大的、满的数组,你会更快地耗尽内存。)
在Java中,如果你解引用一个对象(它超出了作用域),它就会被垃圾收集,所以你必须保持对它的引用,这样才会有内存问题。

qrjkbowd

qrjkbowd5#

1.在类范围内创建对象集合
1.定期向集合中添加新对象
1.不要删除对保存集合的类的示例的引用
由于始终存在对集合和拥有该集合的对象的示例的引用,因此垃圾回收器永远不会清理该内存,从而随着时间的推移会导致“泄漏”。

13z8s7eq

13z8s7eq6#

从我读到的投票最多的答案来看,你很可能是在问一个类似C的内存泄漏问题。好吧,既然有垃圾收集,你就不能分配一个对象,丢失它所有的引用,让它仍然占用内存--那将是一个严重的JVM bug。
另一方面,你可能碰巧泄漏线程--当然,这会导致这种状态,因为你可能会有一些线程在运行时引用了对象,你可能会丢失线程的引用。你仍然可以通过API获得线程引用--参见http://www.exampledepot.com/egs/java.lang/ListThreads.html

xeufq47z

xeufq47z7#

如果使用下面这个极度人为设计的Box类,将会泄漏内存。(准确地说,是在另一次调用put之后......前提是同一对象没有被重新-put到其中。)外部世界无法访问它们。它们不能通过此类解引用,然而这个类确保了它们不能被收集。2这是一个真实的的泄漏。3我知道这是人为的,但是类似的情况也可能是偶然的。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;

public class Box <E> {
    private final Collection<Box<?>> createdBoxes = new ArrayList<Box<?>>();
    private final Stack<E> stack = new Stack<E>();

    public Box () {
        createdBoxes.add(this);
    }

    public void put (E e) {
        stack.push(e);
    }

    public E get () {
        if (stack.isEmpty()) {
            return null;
        }
        return stack.peek();
    }
}
yzuktlbb

yzuktlbb8#

尝试以下简单类:

public class Memory {
    private Map<String, List<Object>> dontGarbageMe = new HashMap<String, List<Object>>();

    public Memory() {
        dontGarbageMe.put("map", new ArrayList<Object>());
    }

    public void useMemInMB(long size) {
        System.out.println("Before=" + getFreeMemInMB() + " MB");

        long before = getFreeMemInMB();
        while ((before  - getFreeMemInMB()) < size) {
            dontGarbageMe.get("map").add("aaaaaaaaaaaaaaaaaaaaaa");
        }

        dontGarbageMe.put("map", null);

        System.out.println("After=" + getFreeMemInMB() + " MB");
    }

    private long getFreeMemInMB() {
        return Runtime.getRuntime().freeMemory() / (1024 * 1024);
    }

    public static void main(String[] args) {
        Memory m = new Memory();
        m.useMemInMB(15);  // put here apropriate huge value
    }
}
kgqe7b3p

kgqe7b3p9#

看起来大多数的答案都不是C风格的内存泄漏。
我想我应该添加一个库类的例子,这个库类有一个bug,它会给予你一个内存不足的异常。同样,这不是一个真正的内存泄漏,但它是一个你不会想到的内存不足的例子。

public class Scratch {
    public static void main(String[] args) throws Exception {
        long lastOut = System.currentTimeMillis();
        File file = new File("deleteme.txt");

        ObjectOutputStream out;
        try {
            out = new ObjectOutputStream(
                    new FileOutputStream("deleteme.txt"));

            while (true) {
                out.writeUnshared(new LittleObject());
                if ((System.currentTimeMillis() - lastOut) > 2000) {
                    lastOut = System.currentTimeMillis();
                    System.out.println("Size " + file.length());
                    // out.reset();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class LittleObject implements Serializable {
    int x = 0;
}

您可以在 JDK-4363937: ObjectOutputStream is creating a memory leak 找到原始代码和错误描述

相关问题