Scoped Variables 和 WeakReferences 交互很奇怪——有些对象没有被垃圾回收 [英] Scoped Variables and WeakReferences interact strangely - some objects don't get garbage collected

查看:17
本文介绍了Scoped Variables 和 WeakReferences 交互很奇怪——有些对象没有被垃圾回收的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Java 程序中看到一些奇怪的行为,我想知道这种行为是否符合预期,以及是否在任何地方都有记录.

I am seeing some strange behavior in a Java program, and I'm wondering if the behavior is expected, and if it's documented anywhere.

我将一些 WeakReference 对象放入一个集合中.(是的,我知道我应该使用 WeakHashMap —— 它具有相同的奇怪行为,这不是这个问题的内容.)

I am placing some WeakReference objects into a collection. (Yes, I know I should use WeakHashMap -- it has the same odd behavior, and that's not what this question is about.)

在某些情况下,最后一个 WeakReference 所引用的对象没有按照我的预期进行垃圾回收.

In some circumstances, the object referenced by the last WeakReference placed into the collection does not get garbage collected when I expect it to.

下面是一组单元测试,显示了我所看到的行为.所有这些测试都按原样通过,并且在评论中可以看到奇怪的行为.(使用 Oracle JDK 1.8 和 OpenJDK 11 进行测试.)

Below, there is a collection of unit tests that show the behavior I'm seeing. All of these tests pass as written, and there are comments where the odd behavior is seen. (Tested using Oracle JDK 1.8 and OpenJDK 11.)

在第一个测试中,我将一个 WeakReference 插入到一个从函数调用返回的对象的集合中:

In the first test, I am inserting into the collection a WeakReference to an object that is returned from function call:

List<WeakReference<Person>> refs = Lists.newArrayList();
refs.add(new WeakReference(getPerson("abc")));

被引用的对象都按预期进行垃圾回收.

The objects that are referenced all get garbage collected as expected.

在第二个测试中,我创建了一个作用域变量来保存函数的返回对象,为其创建一个 WeakReference,并将其插入到集合中.然后变量超出范围,这似乎应该删除任何引用.除了最后一种情况外,在所有情况下都是如此:它们引用的对象会被垃圾收集.但最后一个悬而未决.

In the second test, I have create a scoped variable to hold the function's returned object, create a WeakReference to it, and insert that into the collection. The variable then goes out of scope, which seems like it should remove any reference. In all but the last case, this is true: the objects they reference get garbage collected. But the last one hangs around.

List<WeakReference<Person>> refs = Lists.newArrayList();
{
    Person person = getPerson("abc");
    refs.add(new WeakReference(person));
}

在第三个测试中,我添加了一个额外的临时作用域,并显式使用了一个未添加到集合中的额外作用域变量.集合中所有带有引用的项目都会被正确地垃圾回收.

In the third test, I add an additional temporary scope, and explicitly use an additional scoped variable that doesn't get added to the collection. All of the items with references in the collection get garbage collected properly.

List<WeakReference<Person>> refs = Lists.newArrayList();
{
    Person person = getPerson("abc");
    refs.add(new WeakReference(person));
}
...
{
    Person person = null;
}

在第四个测试中,因为我很好奇行为是否与所有具有相同名称的变量有关——它们是否以某种方式被解释为同一个变量?-- 我为所有临时变量使用了不同的名称.集合中所有带有引用的项目都会按预期进行垃圾回收.

And in the fourth test, since I was curious if the behavior were related to the variables all having the same name -- were they somehow being interpreted as the same variable? -- I used different names for all of the temporary variables. All of the items with references in the collection get garbage collected as expected.

List<WeakReference<Person>> refs = Lists.newArrayList();
{
    Person person1 = getPerson("abc");
    refs.add(new WeakReference(person1));
}
...
{
    Person person4 = null;
}

我能想到的唯一解释是,JRE 以某种方式维护着对正在创建的最后一个对象的引用,即使它超出了范围.但我没有看到任何描述它的文档.

The only explanation I can come up with is that somehow the JRE is maintaining a reference to the last object being created, even though it goes out of scope. But I haven't seen any documentation that describes it.

更新 1:新的测试/解决方法:

Updated 1: a new test/workaround:

如果我在超出范围之前将作用域变量显式设置为 null,则对象会按预期进行垃圾回收.

If I explicitly set the scoped variable to null before it goes out of scope, the objects get garbage collected as I would expect.

List<WeakReference<Person>> refs = Lists.newArrayList();
{
    Person person = getPerson("abc");
    refs.add(new WeakReference(person));
    person = null;
}

<小时>

更新 2:另一个新测试:


Updated 2: Another new test:

新的、无关的对象不需要是相同的类型.这工作正常.

The new, extraneous object doesn't need to be of the same type. This works fine.

List<WeakReference<Person>> refs = Lists.newArrayList();
{
    Person person = getPerson("abc");
    refs.add(new WeakReference(person));
}
...
{
    String unused = "unused string";
}

<小时>

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import static org.testng.Assert.assertEquals;

public class WeakReferenceCollectionTest {
    private static final Logger logger = LoggerFactory.getLogger(WeakReferenceCollectionTest.class);

    static class Person {
        private String name;

        public Person() {

        }

        public String getName() {
            return name != null ? name : "<null>";
        }

        public Person setName(String name) {
            this.name = name;
            return this;
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                              .add("name", name)
                              .toString();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            final Person person = (Person) o;
            return Objects.equals(name, person.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    }

    @Test
    public void collectionWorksAsExpected() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        refs.add(new WeakReference(getPerson("abc")));
        refs.add(new WeakReference(getPerson("bcd")));
        refs.add(new WeakReference(getPerson("cde")));

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);

        refs.add(new WeakReference(getPerson("def")));
        refs.add(new WeakReference(getPerson("efg")));
        refs.add(new WeakReference(getPerson("fgh")));

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);
    }

    @Test
    public void collectionWithScopesWorksDifferently() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        {
            Person person = getPerson("abc");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("bcd");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("cde");
            refs.add(new WeakReference(person));
        }

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 1); // last one never goes away
        assertEquals(refs.get(0).get().getName(), "cde");

        {
            Person person = getPerson("def");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("efg");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("fgh");
            refs.add(new WeakReference(person));
        }

        assertEquals(refs.size(), 4); // previous last one is still in there

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 1); // last one never goes away
        assertEquals(refs.get(0).get().getName(), "fgh");
    }

    @Test
    public void collectionWithScopesAndNewVariableSetToNull() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        {
            Person person = getPerson("abc");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("bcd");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("cde");
            refs.add(new WeakReference(person));
        }
        {
            Person person = null;
        }

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);
    }

    @Test
    public void collectionWithScopesAndDifferentVariableNames() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        {
            Person person1 = getPerson("abc");
            refs.add(new WeakReference(person1));
        }
        {
            Person person2 = getPerson("bcd");
            refs.add(new WeakReference(person2));
        }
        {
            Person person3 = getPerson("cde");
            refs.add(new WeakReference(person3));
        }
        {
            Person person4 = null;
        }

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);
    }

    @Test
    public void collectionWithScopesAndExplicitlySetToNull() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        {
            Person person = getPerson("abc");
            refs.add(new WeakReference(person));
            person = null;
        }
        {
            Person person = getPerson("bcd");
            refs.add(new WeakReference(person));
            person = null;
        }
        {
            Person person = getPerson("cde");
            refs.add(new WeakReference(person));
            person = null;
        }

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);
    }

    @Test
    public void createUnrelatedVariable() throws InterruptedException {
        List<WeakReference<Person>> refs = Lists.newArrayList();
        {
            Person person = getPerson("abc");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("bcd");
            refs.add(new WeakReference(person));
        }
        {
            Person person = getPerson("cde");
            refs.add(new WeakReference(person));
        }
        {
            String unused = "unused string";
        }

        assertEquals(refs.size(), 3);

        System.gc();
        Thread.sleep(1000);

        evictDeadRefs(refs);

        assertEquals(refs.size(), 0);
    }

    private void evictDeadRefs(List<WeakReference<Person>> refs) {
        final Iterator<WeakReference<Person>> it = refs.iterator();
        while (it.hasNext()) {
            final WeakReference<Person> ref = it.next();
            if (ref.get() == null) {
                logger.debug("evictDeadRefs(): removing ref");
                it.remove();
            } else {
                logger.debug("evictDeadRefs(): ref is not null: " + ref.get());
            }
        }
    }

    private Person getPerson(String s) {
        return new Person().setName(s);
    }
}

推荐答案

我认为您看到了一些与 Java 代码如何编译为字节码的交互.需要注意的两个重要事项:

I think you are seeing some interactions with how the Java code is compiled to byte code. Two important things to note:

  1. 垃圾收集器不保证何时,甚至是否会收集对象.保证对象不会出现.
  2. 字节码没有局部变量".相反,它有一个本地堆栈,有许多堆栈帧.局部变量被转换为堆栈帧中的特定位置.

由于#1,Java 的作用域大括号不需要实现为新的堆栈框架.相反,java 编译器可以为整个方法创建一个堆栈帧,并以与作用域规则一致的方式使用它.这意味着,在第二个测试中,局部变量 person 由一个栈帧索引表示,该索引一直存在到方法结束,防止垃圾收集.

Because of #1, the scoping curly braces of Java are not required to be implemented as a new stack frame. Instead, the java compiler can create one stack frame for the whole method, and use it in a way that is consistent with the scoping rules. This means, in the second test, the local variable person is represented by a stack frame index that lives until the end of the method, preventing garbage collection.

因为#2,并且因为局部变量在使用之前必须被初始化,java编译器可以重用一个堆栈帧的一个索引来表示多个局部变量,只要它们中没有两个在作用域内同时.因此,测试 3 和 4 中所有不同的"person 局部变量最终都位于堆栈中的相同位置.

Because of #2, and because local variables must be initialized before they are used, the java compiler can reuse one index of a stack frame to represent multiple local variables, so long as no two of them are ever in scope at the same time. Thus, all of your "different" person local variables in tests 3 and 4 end up being the same location on the stack.

TL;DR:不要期望垃圾收集是一致的.收集对象的时间可能会受到您使用的 JVM GC 以及 Java 编译器的具体实现细节的影响.

TL;DR: Don't expect garbage collection to be consistent. When an object is collected can be affected both by which JVM GC you are using, and by the implementation specific details of your Java compiler.

这篇关于Scoped Variables 和 WeakReferences 交互很奇怪——有些对象没有被垃圾回收的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆