我有一个简单的例子。该示例从包含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
无法访问,为什么不能释放它?
答案 0 :(得分:28)
上述代码中没有内存泄漏。
只要您将代码块保留在{}
中,变量l
就会超出范围,而List
是垃圾收集的候选者,无论您是否设置它首先是null
。
然而,在代码块之后,直到返回方法,List
处于一个名为 invisible 的状态。虽然这是事实,但JVM不太可能自动使引用无效并收集List
的内存。因此,显式设置l = null
可以帮助JVM在进行内存计算之前收集内存。否则,它将在方法返回时自动发生。
对于不同的代码运行,您可能会获得不同的结果,因为您永远不知道垃圾收集器何时运行。你可以建议你认为它应该使用System.gc()
运行(即使没有设置List
,它甚至可能收集不可见的l = null
),但是没有承诺。它在javadoc for System.gc():
调用gc方法表明Java虚拟机耗费 努力回收未使用的物体以制造记忆 它们目前可用于快速重复使用。当控制返回时 从方法调用中,Java虚拟机已尽最大努力 从所有丢弃的物体中回收空间。
答案 1 :(得分:4)
我认为这里存在一些语义问题。 “内存泄漏”通常意味着通过程序(软件等)将一些数据存储在内存中,并使该程序进入无法再访问内存数据以清理它的状态,从而进入某种情况无法申请该记忆以备将来使用的地方。据我所知,这是一般定义。
“内存泄漏”这个术语的实际使用通常是指编程语言,开发人员可以手动为他打算放在堆上的数据分配内存。这些语言是C,C ++,Objective-C(*)等。例如,“malloc”命令或“new”运算符都为将放置在堆内存空间中的类的实例分配内存。在这样的语言中,如果我们稍后想要清理它们使用的内存(当它们不再需要时),则需要将指针保存到那些如此分配的实例。继续上面的例子,引用使用“new”在堆上创建的实例的指针稍后可以通过使用“delete”命令从内存中“删除”并将指针作为参数传递给它。
因此,对于这样的语言,内存泄漏通常意味着将数据放在堆上,然后是:
现在,在“内存泄漏”的这种定义的上下文中,Java几乎不会发生这种情况。从技术上讲,在Java中,垃圾收集器的任务是确定何时不再引用堆分配的实例或超出范围并清理它们。 Java中没有与C ++“delete”命令等效的命令甚至允许开发人员从堆中手动“解除分配”实例/数据。即使将实例的所有指针设为null也不会立即释放该实例的内存,而是只会使其“垃圾收集”,将其留给垃圾收集器线程,以便在扫描时清理它。 / p>
现在,Java中可能发生的另一件事是永远不要放弃指向某些实例的指针,即使在给定点之后不再需要它们。或者,为某些实例提供的范围对于它们的使用来说太大了。这样,它们将在内存中停留的时间比需要的时间长(或永远,永远意味着直到JDK进程被杀死)并且因此垃圾收集器不会收集它们,即使从功能的角度来看它们应该被清除。这可能导致类似于“内存泄漏”的行为,在更广泛的意义上,“内存泄漏”仅仅代表“当不再需要并且无法清理它时在内存中存储内容”。
现在,正如您所看到的,“内存泄漏”有点模糊,但从我所看到的,您的示例不包含内存泄漏(即使您没有使用l = null的版本)。所有变量都在一个范围很窄的范围内,由accolade块分隔,它们在该块内部使用,当块结束时将超出范围,因此它们将被“正确地”收集垃圾(从功能的角度来看)你的节目)。正如@Keppil所说:将指针设为null将为GC提供更好的提示,以便何时清理它的相应实例,但即使你从未将其设为null,你的代码也不会(不必要)挂起实例,所以没有内存泄漏。
Java内存泄漏的典型示例是将代码部署到Java EE应用程序服务器中,这样它将在所述应用程序服务器的控制之外生成线程(映像启动Quartz作业的servlet)。如果多次部署和取消部署应用程序,则有可能某些线程在取消部署时不会被终止,但也会在部署时(重新)启动,从而使他们以及他们可能创建的任何实例在内存中无用地挂起。
(*)Objective-C的更高版本还提供了以类似于Javas Garbage Collection机制的方式自动管理堆内存的可能性。
答案 2 :(得分:2)
真正的答案是,除非代码是JIT,否则方法体内的所有局部变量都是“可达的”。
Morealso,花括号在字节码中完全没有任何作用。它们仅存在于源级别 - JVM绝对不知道它们。将l
设置为null可以有效地从堆栈中释放引用,因此它是真实的GC。快乐的东西。
如果你使用了另一种方法而不是内联块,那么任何事情都不会发生任何意外。
如果代码是JIT,并且JVM编译器已构建reaching-definitions (also this),则最有可能设置l=null
将无效并且在任何一种情况下都会释放内存。
答案 3 :(得分:1)
问题:如果删除l = null; (没有这一行 代码),这是内存泄漏吗?
不,但如果您执行此“模式”,便于 gc声明内存