可能是重复问题:
如何使用Java创建内存泄漏?
如何最简单地引起Java内存泄漏?
可能是重复问题:
如何使用Java创建内存泄漏?
如何最简单地引起Java内存泄漏?
在Java中,除非你:
我认为你对最后一种情况感兴趣。常见的场景有:
一个很好的例子是:
StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
// do nothing...
}
})
注册的操作什么也不做,但它将导致模态窗口永远停留在内存中,即使在关闭后,也会导致泄漏 - 因为监听器从未被注销,并且每个匿名内部类对象都持有对其外部对象的引用(不可见)。更重要的是 - 从模态窗口引用的任何对象也有可能泄漏。
这就是为什么EventBus等库默认使用弱引用的原因。
除了监听器,其他典型的例子是缓存,但我想不出一个好的例子。
首先,我们需要就什么是内存泄漏达成一致。
维基百科曾经这样描述过内存泄漏:在这个链接里:
计算机科学中的内存泄漏(或泄露)指的是当计算机程序消耗了内存但无法将其释放回操作系统时发生的情况。
然而这个定义已经多次更改了,当前(02/2023),维基百科上这样描述:
在计算机科学中,内存泄漏是一种资源泄漏类型,它会发生在计算机程序错误地管理内存分配时,不再需要的内存未被释放的情况下。
取决于上下文,您需要更准确地指定您所要查找的内容。
首先,让我们快速看一个没有自动内存管理的语言的例子:在C语言中,您可以使用malloc()
来分配一些内存。此函数返回指向已分配内存的指针。您必须使用正好该指针调用free()
以将内存释放回操作系统。但是,如果指针在多个地方被使用,谁来负责调用free()
?如果您过早释放内存,则仍在使用该内存的应用程序的某些部分将会崩溃。如果您不释放内存,则会出现泄漏。如果所有分配的内存的指针都丢失(被覆盖或生命周期结束),则您的应用程序将无法将内存释放回操作系统。这符合维基百科2011年对内存泄漏的旧定义。为了避免这种情况,您需要一种规约,用于定义谁负责释放已分配的内存。这需要文档,必须被正确阅读、理解和遵循,可能有很多人创建各种机会出错。
自动内存管理(Java具备)使您摆脱了这种危险。在Java中,您可以使用关键字new
来分配内存,但是没有free
。 new
返回一个“引用”,在这个上下文中类似于指针。当所有对已分配内存的引用都丢失(被覆盖或生命周期结束)时,这会自动检测到并将内存返回给操作系统。
在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
列表都会增长。但是为什么?只需要一个值,而不是整个历史记录。这个类保存了不必要的内存,这符合维基百科对内存泄漏的定义。
在这种情况下,错误是显而易见的。但在更复杂的情况下,可能不那么容易确定什么仍然是“需要”的,什么不是。
无论如何,把东西放到static
变量中是开始麻烦的“好”做法。如果在上面的示例中history
不是static
,那么该类的用户最终可能会释放对Demo
实例的引用,从而释放内存。但由于它是静态的,所以直到整个应用程序终止,历史将一直存在。这里有一个简单的例子
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!");
}
}
}
使用:
public static List<byte[]> list = new ArrayList<byte[]>();
在不删除它们的情况下添加(大)数组。在某些点上,您会意识到自己已经用完了内存,而不怀疑它。(您可以对任何对象执行此操作,但是对于大型、完整的数组,您会更快地用完内存。)
在Java中,如果您取消引用一个对象(它超出范围),它将被垃圾回收。因此,您必须保持对它的引用,以便出现内存问题。
由于集合和拥有集合的对象实例始终存在引用,因此垃圾收集器永远不会清理该内存,随着时间的推移可能会导致"泄漏"。
Box
类在使用时会泄露内存。被 put
进入该类的对象最终(确切地说是在另一个对 put
的调用之后......前提是没有重新将相同的对象put
进去)无法通过该类从外部引用。它们不能通过该类取消引用,但该类确保它们不能被收回。这是一个真正的内存泄漏。我知道这非常牵强,但类似的意外情况是有可能发生的。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();
}
}
尝试使用这个简单的类:
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
}
}
看起来大多数答案都不是C风格的内存泄漏。
我想举一个库类的例子,它有一个错误会导致你得到一个内存不足异常。虽然这不是真正的内存泄漏,但它是一个意料之外的内存耗尽的例子。
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找到原始代码和错误描述。