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:
action
know on which Foo
instance it should be invoked? I assume so, otherwise it'd be simple.action
prevent its stored foo
from GC? I assume no, as this is what you're asking.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:
action
store a WeakReference<Foo>
.SoftReference
unless you want to keep foo
as long as possible.foo
, check if get
returned null
.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.