简体   繁体   中英

Java 8 lambda weak reference

I create an object called Foo . When I create a lambda or method reference called Action , the Action object holds a reference to Foo .
I pass the action to another class. But if I hold it as a weak reference, it gets gc immediately, because no one stores another reference to Action .
But if i hold it as a strong reference, the Foo can't be gc, because Action holds a referene to it.
So memory leaks happen and I want to prevent it.

My question is: how can I hold a reference to Action without preventing gc of Foo .

Example:

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();
}

You have to separate the action from the weakly referenced target . You should always keep in mind that lambdas are intended to specify behavior only.

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();
  }
}

The inverse strategy would be to register all Actions to the Foo. That seems not to fit.

Why not SoftReference ? Then the Action has to check, but that is what seems to be your intention.

You could additionally register the Action at the foo (addListener, inverse strategy), to signal a change of state to "dying."

My question is: how can I hold a reference to Action without preventing gc of Foo.

I'd say that a few things need to be clarified first:

  • Should action know on which Foo instance it should be invoked? I assume so, otherwise it'd be simple.
  • Should action prevent its stored foo from GC? I assume no, as this is what you're asking.
  • Should the foo prevent its action from GC? I assume yes, as it'd be too strange.

So you can have no strong reference from action to foo . This means no lambdas (if I understand them correctly). A strong reference from foo to action would be OK, but you don't seem to want to store them there.

I guess, it's doable, albeit a bit complicated:

  • In action store a WeakReference<Foo> .
  • Don't use SoftReference unless you want to keep foo as long as possible.
  • Before doing anything with foo , check if get returned null .
  • Run some cleanup in the holder to get rid of Actions having lost their Foo s ( ReferenceQueue is your friend).

I stumbled upon this problem just now. I tried several things and they don't work.

Typically I have a StringHolder that can be assigned a String, with method set() and listen(). I also have a IntReceiver that contains the int it received, with method set(value) and get().

The typical use case is :

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);

And I tried several implementations. They can't work simply. I tried double weak ref, I tried other things I can't even remember.

The only way to work, is to force the receiver to keep a reference to the lambda. So I need a new interface RefStore, with store(Object ref) method, and I make that IntReceiver implement it ; the implementation is just a linkedlist.

Then the listen() needs to take a RefStore as parameter, and store the lambda in the refstore. The usage code becomes

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

And then your lambda can safely be stored in weak references, to be removed when a new value is set and the ref is null.

Here is my 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);
            }
        }
    }

}

Here is my 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;
    }

}

Here is the main :

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);
    }

}

Here is the force() method to force the GC.

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");
}

Here is the output :

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

as you can see, after the call to add() a listener is added but not gc yet ; once forced the GC however it is collected. The issue is that the set() method must be called to check for null refs and remove them.

The main issue is that this way of making it work requires to tweak a lot with classes. Especially a problem if you want to have lambda do things, eg syserr, without storing the data. It would be better to be able to attach a lambda to an object so that lambda is considered strong referenced .

One more thing I need to try, is to try to work with finalize in a subclass of weakReference, to avoid actually finalizing the ref until another weak reference is actually GC'd.

edit : I added a few more test in the main, by adding to listeners using print, one of them also using the parameter(dynamic) and the other one not using it (static) :

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 -> {
        });
    }

}

Here is the result :

 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

as you can see, the dynamic listener is GC, but not the static one, since the lambda is static and therefore referenced as long as the class is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM