Java内存泄漏的基础知识

3
我看到过一个问题Java是按值传递。所以,假设我们有以下代码,并且HashMap somehashMap的生命周期比foo长。因此,即使它完成了工作,我们将foo放入Map中然后忘记从中删除,也不允许垃圾回收该对象。现在根据我链接中的答案的逻辑,我们实际上是将foo的引用的副本传递给了put()方法,这样放置foo进入HashMap不应该防止它被垃圾回收。请您帮助我理解这里发生了什么?我到底错过了什么?
 public void someMethod(){
     Foo foo  = new Foo();
     somehashMap.put(fooKey,foo); 
  }

1
Foo 的生命周期与 somehashMap 相同。 - Chris Dargis
9个回答

11

垃圾收集不适用于引用,而是适用于存储在堆中的实际对象。当你将foo放入Map时,你基本上帮助它“逃脱”了当前作用域,并将其放在与somehashMap相同的作用域/生命周期中。

在Java中,引用是在幕后透明处理的。当你把foo引用放入map中时,实际上会传递引用 的副本给put方法调用,但基础对象即new Foo() 对于原始和复制的引用都是相同的。让我们看下面的代码片段:

public void doIt() {
  Object f1 = new Object();
  Object f2 = f1;
  Object f3 = f2;
}
在上面的代码片段中,当执行doIt完成后,有多少个对象被垃圾回收?只有我们创建的单个new Object()对象。其余的都只是引用或别名,用于指向同一个对象。

7
只要对象仍然可达,就无法进行垃圾回收。在您的情况下,foo 可以通过 Foo foo = someHashMap.get(fooKey); 达到,因此只要它在映射中并且该映射本身是可达的,就无法进行垃圾回收。

6
我不认为你清楚引用实际上是什么。
你部分正确,'foo' 是一个对象的引用,将其添加到哈希映射表中有效地创建了对同一对象的第二个引用。
然而,这正是为什么当你的 'foo' 变量消失时,对象不会被垃圾回收的原因。对象只有在没有引用时才会被垃圾回收 - 你开始拥有一个引用(foo),然后创建了第二个引用(在哈希映射表内),当函数结束时第一个引用消失了,但你仍然有一个引用。

6

试一下图片:

输入图像描述

盒子是对象。未装箱的字母是变量。

在开始时,您只有哈希映射表,我假设它存在于函数调用之前,并继续存在于函数调用之后。

然后new Foo()创建一个新对象,f=创建一个变量引用它。

然后hashmap.put(k,f)在哈希映射表中创建一个条目,该条目指向与f相同的对象。

然后,当您退出函数时,局部变量f将不再存在,但其他所有内容仍然存在。

垃圾回收器不会删除Foo对象,因为哈希映射表仍然指向它。这就是您想要的-如果稍后调用hashmap.get(k),您将期望获得该Foo对象。

如果从哈希映射表中删除条目-hashmap.remove(k),则可以对Foo对象进行垃圾回收。

如果哈希映射表本身停止存在,则可以对Foo对象进行垃圾回收。


3

大多数支持垃圾回收器的语言都支持某些类型的弱引用。例如,你可以在Java中找到一个弱哈希映射实现。如果你将foo对象放入弱哈希映射中并且垃圾回收器运行,则如果除了弱指针之外没有其他指向foo的指针,则foo对象将被收集并从弱哈希映射中删除。有时这种方法可以避免内存泄漏。


2
只要 foo 在 HashMap 中,它就不会被垃圾回收,因为 Java 不知道您是否会再次使用它。只有当所有引用都已结束生命周期时,foo 才能被垃圾回收。请保留 HTML 标签。

2

我们实际上是将 foo 的引用的副本传递给 put() 方法,对吗?

是的,Java 传递的是引用的副本,而这个引用指向内存中的同一个对象(在堆中),因此直到 map 被回收之前,该对象都不会被 GC 回收。


1

在此put方法中,传递的值是对foo引用的值。由于foo是引用计数的,只要somehashMap保持引用,foo就不会被垃圾回收。

我在继承的一些代码中发现了这样的内存泄漏,其中有人使用映射来缓存图像,但当再次需要图像时,没有获取已缓存的图像,而是创建了一个新的图像,并将其推入带有新键的映射中。


1

Java确实是按值传递的。但是你传递的是一个引用按值传递,而不是一个对象的实例


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