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
Nested spies cause memory leaks #1532
Comments
@mockitoguy, sorry for the ping, but the situation gets worse on our side. Is the issue with Mockito or we are just using it the wrong way? |
CC @raphw |
You are right, due to the reference of one mock to another, the weak map is loosing its effect. Technically, we would need to make the spy reference weak to break the cross reference but I sm not sure how this can be achieved without risking to have the references collected prematurely. |
@raphw, thanks for the explanation! If it is more or less intended — maybe let’s document it somewhere? Another option is throwing an exception or printing a warning in such cases. Since we weren’t aware of this and use Mockito quite a lot we got ourselves in a pretty bad OOM situation. Finding nested spies by hand is a tedeous task, not even sure I can automate it without library support. I’m afraid people will continue to do this not understanding the consequences. Can I ask you to take a look at #1533 as well? I think it is pretty similar or even exactly the same in terms of the root issue. |
It is surely not expected behavior and I consider it a bug. However, I do not know how to solve this and I even doubt that it is solvable without a data structure that is called an https://en.wikipedia.org/wiki/Ephemeron and which Java does not (currently) support. But we should definitely document this. |
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their lifespan, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their lifespan, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. User can call MockitoSessionBuilder#trackAndCleanUpMocks() to let the session track and clean up mocks. It also relies on implementation of mock makers to cleanUpMock(Object). SubclassByteBuddyMockMaker is a intentional no-op to avoid unnecessary behavior changes in stable features. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. User can call MockitoSessionBuilder#trackAndCleanUpMocks() to let the session track and clean up mocks. It also relies on implementation of mock makers to cleanUpMock(Object). SubclassByteBuddyMockMaker is a intentional no-op to avoid unnecessary behavior changes in stable features. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. User can call MockitoSessionBuilder#trackAndCleanUpMocks() to let the session track and clean up mocks. It also relies on implementation of mock makers to cleanUpMock(Object). SubclassByteBuddyMockMaker is a intentional no-op to avoid unnecessary behavior changes in stable features. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. User can call MockitoSessionBuilder#trackAndCleanUpMocks() to let the session track and clean up mocks. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Due to the introduction of map from weak reference from mock instance to its invocation handler, Mockito became vunerable to memory leaks as there are multiple situations where Mockito could unintentionally hold strong references to mock instances in the map record. The strong references could be through spiedInstance for spies, and arguments used to faciliate method stubbing. Mockito could never know if the arguments passed in for method stubbing are also strongly referenced somewhere else or not, so Mockito needs to save a strong reference to these arguments to avoid premature GC. Therefore to solve cyclic strong references through arguments Mockito needs to explicitly know when mocks are out of their life, and clean up all internal strong references associated with them. This commit also fixes mockito#1532 and fixes mockito#1533.
Just checked Mockito 2.27.0. Using the new |
Seems like nested spies can cause memory leaks since such objects are kept in memory without purging. Not sure if it can be resolved at all. Should it be avoided? Is there a mention about this in docs? Anyway, the code speaks better and fortunately I’ve been able to create a self-contained sample.
BTW I can provide a
.hprof
file if you are interested.Versions
Gradle
Heap is set to 64 MB.
Code
Eclipse Memory Analyzer
Seems like this happens:
Service
is a spy.Service
contains aConsumer
, it is aspy
as well.Consumer
is a closure and has an implicit reference toService
.The text was updated successfully, but these errors were encountered: