-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeLiteral configured in child Injector leaked into parent Injector #910
Comments
Could you write this as a junit testcase where there's two sibling child injectors that attempt to |
Also: what version of Guice are you using? |
Oh sorry, this is guice-4.0-beta5. It is also an internal repackaged version. I'll double check with pure guice. As for the JUnit test, I don't think the binding is being promoted, it just remembers the TypeLiteral itself for some reason and (since it is an anonymous inner class) that causes the leak. |
Here's what's happening:
All this is retained through WeakKeySet's cache, associated to the child injector's state. Fortunately, AFAICT, this is all working as designed. Unless something else retains a reference to the child injector's state, then when GC happens the state will be cleaned up and everything will get discarded. Do you have reason to believe the state is being held by a strong ref & isn't being GC'd? If so, that'd be a problem. |
Thanks very much for the feedback. I do find it unlikely that Guice has a bug here, but I can't work out what I've done wrong. OK I tried it with How about the following. testBindToClass passes and testBindToTypeLiteral throws OutOfMemoryError for me, when run with -Xmx32m... import org.junit.Test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
public class GuiceTest {
public static class GiantObject {
private final String _name;
private final Object[] _array;
public GiantObject(final String name) {
_name = name;
_array = new Object[5000000];
}
}
@Test
public void testBindToClass() {
Injector parent = Guice.createInjector();
parent.createChildInjector(createModule(new GiantObject("notleaked")));
Object[] array = new Object[5000000];
}
@Test
public void testBindToTypeLiteral() {
Injector parent = Guice.createInjector();
parent.createChildInjector(createTypeLiteralModule(new GiantObject("leaked")));
Object[] array = new Object[5000000];
}
private static AbstractModule createModule(final GiantObject o) {
return new AbstractModule() {
@Override
protected void configure() {
bind(GiantObject.class).toInstance(o);
}
};
}
private static AbstractModule createTypeLiteralModule(final GiantObject o) {
return new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<GiantObject>() {
}).toInstance(o);
}
};
}
} |
That does look suspect. Can you use a memory analyzer to try to find different roots holding the child injector's state or the GiantObject? |
I'm at a loss then. Would you be able to dig into who might be holding the child injector's InternalState object? |
Oh, wait, I know (I think). We don't eagerly clean up the WeakKeySet cache. You have to access or mutate it in some way to trigger it to cleanup. What happens if you do a second call to injector.createChildInjector before creating the second big array? |
That sounds compelling, but... @Test
public void testBindToTypeLiteral() {
Injector parent = Guice.createInjector();
parent.createChildInjector(createTypeLiteralModule(new GiantObject("leaked", 5000000)));
parent.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<GiantObject>() {
}).toInstance(new GiantObject("hmmm", 1));
}
});
Object[] array = new Object[5000000];
} ...doesn't help matters (integer is size of Object[] so that I could create the second instance to try and remove the TypeLiteral). Perhaps triggering cleanup requires additional steps? |
OK, had a look at the Guice code. AFAICT @Test
public void testBindToTypeLiteral_evictionCacheCleanUp() {
Injector parent = Guice.createInjector();
parent.createChildInjector(createTypeLiteralModule(new GiantObject("leaked")));
System.gc(); // Need to provoke gc to get child injector disposed
parent.getInstance(String.class); // Cause a JustInTimeBinding to be created so that evictionCache.cleanUp is invoked
Object[] array = new Object[5000000];
} So it seems like there's a genuine leak here that has the potential to hang around indefinitely (i.e. until a JustInTime binding is required). |
Thanks for taking a closer look, @moreginger! I'll see if there's something more we can do here. |
I did some work in 825f8c1 which should alleviate the problem. It doesn't do any aggressive cleaning of WeakKeySet, but it does make sure that Key/TypeLiteral don't retain references to their surrounding classes, so the WeakKeySet shouldn't ever hold onto more data. When I merge #912, I'll close this since I think that's the best we can do. (I looked into more aggressive cleanup, but there's no good way I could find to do it that wouldn't add a lot of overhead to critical fast paths.) |
Should be fixed in the latest snapshot. Can you give it a go (once it auto-publishes) and see how it works? |
Pulled latest code and that works for me, thanks :) |
Perhaps this behavior is known but I couldn't find any mention of it. This code...
...puts a reference to the TypeLiteral in the parent Injector, and appears never to be garbage collected. JProfiler lists InheritingState/WeakKeySet in the first GC root. This leaks whatever is in scope for the TypeLiteral (i.e. the GiantObject("leaked") instance).
The text was updated successfully, but these errors were encountered: