18

我有一个简单的例子。该示例从包含 10000000 个随机整数ArrayList<Integer>的文件中加载一个。f

doLog("Test 2");
{
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois = new ObjectInputStream(fis);
    List<Integer> l = (List<Integer>) ois.readObject();
    ois.close();
    fis.close();
    doLog("Test 2.1");
    //l = null; 
    doLog("Test 2.2");
}
doLog("Test 2.3");
System.gc();
doLog("Test 2.4");

当我有时l = null,我得到这个日志:

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 493 KB   Total Mem = 123 MB

但是当我删除它时,我得到了这个日志。

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 44 MB    Total Mem = 123 MB

Used Memory由下式计算:runTime.totalMemory() - runTime.freeMemory()

问题:如果l = null;存在,是否存在内存泄漏? l是不可访问的,那为什么不能释放呢?

4

4 回答 4

28

上面的代码中没有内存泄漏。

一旦您离开包含在 中的代码块{},该变量l就会超出范围,并且List无论您是否将其设置为nullfirst,都将成为垃圾回收的候选对象。

但是,在代码块之后直到方法返回,List它处于一种称为不可见的状态。虽然这是真的,但 JVM 不太可能自动清空引用并收集List的内存。因此,显式设置l = null可以帮助 JVM 在您进行内存计算之前收集内存。否则,它将在方法返回时自动发生。

对于代码的不同运行,您可能会得到不同的结果,因为您永远不知道垃圾收集器何时运行。您可以建议您认为它应该运行 using (即使没有设置System.gc()它甚至可能收集不可见的),但没有承诺。它在System.gc() 的 javadoc 中有说明: Listl = null

调用 gc 方法表明 Java 虚拟机花费精力回收未使用的对象,以使它们当前占用的内存可用于快速重用。当控制从方法调用返回时,Java 虚拟机已尽最大努力从所有丢弃的对象中回收空间。

于 2012-08-19T20:45:42.637 回答
4

我认为这里有一些语义问题。“内存泄漏”通常意味着程序(软件等)将一些数据存储在内存中,并使该程序进入无法再访问该内存数据以对其进行清理的状态,从而进入一种情况无法声明该内存以供将来使用。据我所知,这是一般定义。

术语“内存泄漏”在现实世界中的使用通常是指编程语言,在这种语言中,开发人员需要为他打算放在堆上的数据手动分配内存。此类语言是 C、C++、Objective-C (*) 等。例如,“malloc”命令或“new”运算符都为将放置在堆内存空间中的类的实例分配内存。在这样的语言中,如果我们稍后想要清理它们使用的内存(当它们不再需要时),则需要保留指向那些因此分配的实例的指针。继续上面的例子,引用一个使用“new”在堆上创建的实例的指针稍后可以通过使用“delete”从内存中“删除”

因此,对于此类语言,内存泄漏通常意味着将数据放在堆上,然后是:

  • 到达不再有指向该数据的指针的状态,或
  • 忘记/忽略手动“取消分配”堆上数据(通过它的指针)

现在,在这种“内存泄漏”定义的上下文中,Java 几乎不会发生这种情况。从技术上讲,在 Java 中,垃圾收集器的任务是决定何时不再引用堆分配的实例或超出范围并清理它们。Java 中没有与 C++“删除”命令相当的功能,它甚至允许开发人员手动从堆中“取消分配”实例/数据。即使将实例的所有指针设为 null 也不会立即释放该实例的内存,而是只会使其成为“可收集的垃圾”,并将其留给垃圾收集器线程在它进行扫描时对其进行清理。

现在,Java 中可能发生的另一件事是永远不要放弃指向某些实例的指针,即使在给定点之后不再需要它们。或者,给某些实例一个对于它们使用的范围来说太大的范围。这样,它们将在内存中停留的时间比需要的时间长(或者永远,永远意味着直到 JDK 进程被杀死),因此即使从功能的角度来看它们应该被清理,垃圾收集器也不会收集它们。这可能导致类似于广义上的“内存泄漏”的行为,其中“内存泄漏”仅代表“内存中不再需要的东西并且无法清理它”。

现在,如您所见,“内存泄漏”有些模糊,但据我所知,您的示例不包含内存泄漏(即使是您不设置 l=null 的版本)。您的所有变量都在由 accolade 块分隔的紧密范围内,它们在该块内使用,并且在块结束时将超出范围,因此它们将被“正确”收集(从功能的角度来看你的程序)。正如@Keppil 所说:使指针为空将为 GC 提供一个更好的提示,说明何时清理它的相应实例,但即使您从未将其设为空,您的代码也不会(不必要)挂在实例上,所以那里没有内存泄漏。

Java 内存泄漏的一个典型示例是,将代码部署到 Java EE 应用程序服务器中时,它将产生不受所述应用程序服务器控制的线程(想象一个启动 Quartz 作业的 servlet)。如果应用程序被多次部署和取消部署,那么一些线程可能不会在取消部署时被杀死,但也会在部署时(重新)启动,从而使它们和它们可能创建的任何实例在内存中无用地挂起。

(*) 更高版本的 Objective-C 还提供了自动管理堆内存的可能性,其方式类似于 Java 的垃圾收集机制。

于 2012-08-19T21:29:38.950 回答
2

真正的答案是,除非代码是 JIT 的,否则所有局部变量在方法主体内都是“可访问的”。

此外,大括号在 bytecode 中绝对没有任何作用。它们只存在于源代码级别——JVM 完全不知道它们。设置l为 null 可以有效地将引用从堆栈中释放出来,因此它是真正的 GC。快乐的东西。

如果您使用另一种方法而不是内联块,那么一切都会过去而没有任何意外。

如果代码是 JIT 的并且 JVM 编译器已经构建了到达定义 (也是这个) ,那么很可能设置l=null将不起作用并且在任何一种情况下都会释放内存。

于 2012-08-22T16:12:16.530 回答
1

问题:如果删除 l = null; (没有这行代码),这是内存泄漏吗?

不,但是如果您执行此“模式”,它有助于gc 声明内存

于 2012-08-19T20:54:50.697 回答