Java 8 lambda弱引用

6
我创建了一个名为Foo的对象。当我创建一个名为Action的lambda或方法引用时,Action对象持有对Foo的引用。
我将该操作传递给另一个类。但是,如果我将其保存为弱引用,则它会立即被垃圾回收,因为没有人存储对Action的另一个引用。
但是,如果我将其保存为强引用,则Foo无法被垃圾回收,因为Action持有对它的引用。
因此,内存泄漏发生了,我想要防止它发生。

我的问题是:如何保留对Action的引用而不会阻止Foo的垃圾回收。

示例:

Interface Action {
    void invoke();
}

Class Foo() {
    public void someMethod() {
        ....
    }
}

Class Holder() {
     WeakRefrence<Object> foo;
     public Action action;

     void register(Object source, Action a) {
         foo = new WeakReference(source);
         ??? how can i hold the action without prevent source to gc able.
     }
}


main() {
    Holder holder = new Holder();
    Foo foo = new Foo();
    Action action = foo::someMethod;

    holder.register(foo,action);
    action = null;
    System.gc();
    //only the holder store reference to action.
    //if i store in holder as weak reference i cannot invoke it any more cause it get gc.

    //foo = null;
    //System.gc();
    //if i grab action in holder as strong refrence the foo cant be gc cause action hold refernce to it.

    holder.action.invoke();
}

请移除这个 System.gc(); - Marco Acierno
我无法理解这个问题。如果foo已经被垃圾回收,那么action永远不会被调用,因为它引用了foo的一个实例方法。保留action但让foo被垃圾回收就像是在说:“我想保留一个ArrayList,但让它的所有项都被垃圾回收。”这样做是没有意义的。 - David Conrad
我想在C#中创建类似于WeakAction的东西。 - Lege
4个回答

4

您需要将操作与弱引用的目标分开。您应该时刻记住,lambda表达式仅用于指定行为

class Foo {
    public void someMethod() {
       System.out.println("Foo.someMethod called");
       // ....
    }
}

class Holder<T> extends WeakReference<T> {
    private final Consumer<T> action;
    Holder(Consumer<T> action, T target) {
        super(target);
        this.action=action;
    }
    public void performAction() {
        T t=get();
        if(t!=null) action.accept(t);
        else System.out.println("target collected");
    }
}

class Test {
  public static void main(String... arg) {
    Foo foo = new Foo();
    Holder<Foo> holder = new Holder<>(Foo::someMethod, foo);
    System.gc(); // we are still referencing foo
    holder.performAction();
    foo = null;
    System.gc(); // now we have no reference to foo
    holder.performAction();
  }
}

我尝试过了,但没有起作用。该操作仍然引用foo。也许我做错了什么? - Lege
2
当我测试它时,它是有效的。也许你只是遇到了 System.gc() 只是一个提示的事实。此外,在调试它时它不会工作,因为调试器将保留对其已见对象的引用。您可以使用像 JVisualVM 这样的性能分析工具来创建和检查堆转储。 - Holger

0

相反的策略是将所有操作都注册到Foo。这似乎不合适。

为什么不使用 SoftReference?然后,Action必须进行检查,但这似乎是您的意图。

您还可以通过将Action注册到foo(addListener,反向策略)来发出状态更改信号为“正在死亡”。


如果我使用软引用来持有Action,当Java内存不足时它最终会被垃圾回收,但我想在源代码仍在内存中(具有硬引用)时持有对Action的引用。您还可以在foo上注册Action(addListener,反向策略),以向“dying”状态发出状态更改信号。这将创建一个循环引用,Foo持有Action并且Action持有Foo,因此它们都无法被垃圾回收,但也许我误解了您的建议。 - Lege
不,你对于循环引用的理解是正确的;通常情况下,我们会采取一些措施,比如创建一个类来保持弱监听器。我不确定所有的细节,但如果你能在Foo中说清楚,当它被释放时,可以清理所有的监听器并向它们发送一条消息,将它们的Foo设置为null。恐怕只有你能找到合适的解决方案。我不知道是否有任何神奇的类,尽管愚人节可能会诱惑你相信有这样的类存在。 - Joop Eggen

0
我的问题是:如何在不阻止Foo的垃圾回收的情况下保持对Action的引用。
首先需要澄清一些事情:
- action是否知道应在哪个Foo实例上调用?我认为是的,否则它将很简单。 - action是否会防止其存储的foo被GC?我认为不会,因为这就是你要问的。 - foo是否会防止其action被GC?我认为是的,因为这太奇怪了。
因此,您不能从action到foo有强引用。这意味着没有lambda(如果我正确理解的话)。从foo到action的强引用是可以的,但您似乎不想在那里存储它们。
我猜,这是可行的,尽管有点复杂:
  • action存储一个WeakReference<Foo>
  • 除非您想尽可能长时间地保留foo,否则不要使用SoftReference
  • 在对foo执行任何操作之前,请检查get是否返回了null
  • holder中运行一些清理操作,以摆脱已丢失其FooActionReferenceQueue是您的朋友)。

"在 holder 中运行一些清理操作" -> 那为什么不在 holder 中使用 WeakHashmap 呢?我明白 holder 不知道 foo。所以你不能这样做。 - Claude Martin
假设我正确理解了问题,每个“action”应该与其对应的“foo”一样长寿,但是“WeakHashMap”会立即失去它们(因为它们在其他地方没有被引用)。清理可能只需要遍历“action”,并删除那些已经失去了它们的“foo”的“action”。持有者知道所有的“action”,并询问它们是否仍然引用它们的“foo”。 - maaartinus
但是Action只是一个接口,您不能查询它的foo属性。 我认为只有了解WeakAction的人才能回答这个问题。 - Claude Martin
@СӏаџԁеМаятіи 如果Action中应该有一个弱引用,那么你需要一些具体的实现。如果只是简单地写Action action = foo::someMethod;,你将得不到任何弱引用...也许这并不能按照我想要的方式工作。 - maaartinus

0

我刚刚遇到了这个问题。我尝试了几种方法,但它们都不起作用。

通常我有一个StringHolder,可以分配一个字符串,具有set()和listen()方法。 我还有一个IntReceiver,其中包含它接收到的整数,具有set(value)和get()方法。

典型的用例是:

StringHolder sh= new StringHolder();
IntReceiver ir = new IntReceiver();
sh.listen(str->ir.set(str.length());//should test null
sh.set("123");
assert(ir.get()== 3);

我尝试了几种实现方式,但它们都不能简单地工作。我尝试了双重弱引用,还尝试了其他一些我甚至都记不起来的东西。

唯一可行的方法是强制接收器保持对lambda的引用。因此,我需要一个新的接口RefStore,其中包含store(Object ref)方法,并使IntReceiver实现它;实现只是一个链表。

然后,listen()需要将RefStore作为参数,并将lambda存储在refstore中。使用代码变成:

sh.listen(str->ir.set(str.length(), ir);

然后你的lambda表达式可以安全地存储在弱引用中,在设置新值并且引用为空时将其删除。

这是我的StringHolder。

public class StringHolder{

    private List<WeakReference<Consumer<String>>> listeners = new ArrayList<>();

    public int listeners() {
        return listeners.size();
    }

    public void listen(Consumer<String> listener, RefStore holder) {
        holder.store(listener);
        listeners.add(new WeakReference<>(listener));
    }

    public void set(String data) {
        for (Iterator<WeakReference<Consumer<String>>> it = listeners.iterator(); it.hasNext();) {
            Consumer<String> call = it.next().get();
            if (call == null) {
                it.remove();
            } else {
                call.accept(data);
            }
        }
    }

}

这是我的IntReceiver
public class IntReceiver implements RefStore {

    private int value;

    private transient LinkedList<Object> stored = new LinkedList<>();

    @Override
    public void store(Object o) {
        stored.add(o);
    }

    public void set(int val) {
        value = val;
    }

    public int get() {
        return value;
    }

}

这是主要内容:

public class MainExample {

    public static void main(String[] args) {
        IntReceiver ir = new IntReceiver();
        StringHolder sr = new StringHolder();
        sr.listen(s -> ir.set(s.length()), ir);

        sr.set("123");
        System.out.println("value for 123 is " + ir.get());
        System.out.println("calls=" + sr.listeners());
        add(sr);
        sr.set("1234");
        System.out.println("value for 1234 is " + ir.get());
        System.out.println("calls=" + sr.listeners());
        GCManage.force();
        sr.set("132");
        System.out.println("value for 132 is " + ir.get());
        System.out.println("calls=" + sr.listeners());

    }

    protected static void add(StringHolder ls) {
        IntReceiver is = new IntReceiver();
        ls.listen(s -> is.set(s.length()), is);
    }

}

这里是force()方法,用于强制进行垃圾回收。
public static void force() {
    WeakReference<Object> wr = new WeakReference<>(new Object());
    int nb = 0;
    int arraySize = (int) (Math.min(Runtime.getRuntime().freeMemory() / 32 / 8, Integer.MAX_VALUE - 5) / 10);
    while (wr.get() != null) {
        @SuppressWarnings("unused")
        Object[] trash = new Object[arraySize];
        System.gc();
        nb++;
    }
    logger.debug("gc after " + nb + " buffers");
}

这是输出结果:

value for 123 is 3
calls=1
value for 1234 is 4
calls=2
value for 132 is 3
calls=1

正如您所看到的,在调用add()之后,添加了一个监听器但尚未进行垃圾回收;一旦强制进行GC,它就会被回收。问题在于必须调用set()方法来检查空引用并将其删除。

主要问题是这种使其工作的方式需要大量调整类。特别是如果您想让lambda执行某些操作,例如syserr,而不存储数据,则会出现问题。最好能够将lambda附加到对象上,以便将lambda视为强引用。

我还需要尝试的另一件事是,在weakReference的子类中使用finalize来避免实际上在另一个weak reference实际上被GC之前完成引用的最终处理。

编辑:我在主函数中添加了几个测试,通过使用print添加两个监听器之一,其中一个还使用参数(动态),另一个则不使用参数(静态):

public class MainExample {

    public static void main(String[] args) {
        IntReceiver ir = new IntReceiver();
        StringHolder sr = new StringHolder();
        sr.listen(s -> ir.set(s.length()), ir);

        sr.set("123");
        System.out.println(" value for 123 is " + ir.get());
        System.out.println(" calls=" + sr.listeners());
        System.out.println("adding weak referenced listeners");
        add(sr);
        addSysoDynamic(sr);
        addSysoStatic(sr);
        sr.set("1234");
        System.out.println(" value for 1234 is " + ir.get());
        System.out.println(" calls=" + sr.listeners());
        System.out.println("force GC");
        GCManage.force();
        sr.set("132");
        System.out.println(" value for 132 is " + ir.get());
        System.out.println(" calls=" + sr.listeners());

    }

    protected static void add(StringHolder ls) {
        IntReceiver is = new IntReceiver();
        ls.listen(s -> is.set(s.length()), is);
    }

    protected static void addSysoDynamic(StringHolder ls) {
        ls.listen(s -> System.out.println(" received(dynamic) " + s + " from " + ls.getClass().getSimpleName()), o -> {
        });
    }

    // no ref to the argument means the lambda is static and linked to the class
    protected static void addSysoStatic(StringHolder ls) {
        ls.listen(s -> System.out.println(" received(static) " + s), o -> {
        });
    }

}

这是结果:

 value for 123 is 3
 calls=1
adding weak referenced listeners
 received(dynamic) 1234 from StringHolder
 received(static) 1234
 value for 1234 is 4
 calls=4
force GC
 received(static) 132
 value for 132 is 3
 calls=2

正如你所看到的,动态监听器是GC,但静态监听器不是,因为lambda是静态的,所以只要类存在,就会被引用。


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