I’m using Gradle 1.6 and I have a large, 174 project multi-project build which is throwing OutOfMemoryErrors when running tasks against all projects (‘idea’ in this case). I can reproduce with and without the daemon with a 1024m heap. Eclipse MAT’s dominator tree shows that ‘DefaultConfiguration_Decorated’ ‘cachedResolverResults’ instance is the where references are being held.
For example, a configuration for a simple project with a total of 106 dependencies reported by the ‘dependencies’ task for the main configuration consumes 1,707,304 bytes. ‘org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration’ holds references to ‘DefaultResolvedArtifact’ and ‘DefaultResolvedDependency’ where the utilisation is actually occurring. An artifact such as hibernate-core retains 95,152 bytes:
Class Name
| Shallow Heap | Retained Heap | Percentage
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
org.gradle.api.internal.artifacts.configurations.DefaultConfiguration_Decorated @ 0x129fadb50
|
144 |
1,707,304 |
0.18%
|- org.gradle.api.internal.artifacts.ResolverResults @ 0x14c7d0a40
|
24 |
1,702,768 |
0.18%
|
|- org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingArtifactDependencyResolver$ErrorHandlingResolvedConfiguration @ 0x14c7d0a28|
24 |
1,647,432 |
0.17%
|
|
'- org.gradle.api.internal.artifacts.ivyservice.SelfResolvingDependencyResolver$FilesAggregatingResolvedConfiguration @ 0x14c7d0a10
|
24 |
1,647,408 |
0.17%
|
|
|- org.gradle.api.internal.artifacts.ivyservice.DefaultResolvedConfiguration @ 0x14c7d07c0
|
16 |
1,646,824 |
0.17%
|
|
|
'- org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration @ 0x14c7a0808
|
40 |
1,646,808 |
0.17%
|
|
|
|- org.gradle.api.internal.artifacts.DefaultResolvedArtifact @ 0x14c7c4bd0
|
40 |
95,152 |
0.01%
|
|
|
|
|- org.gradle.api.internal.artifacts.ivyservice.ResolvedArtifactFactory$1 @ 0x14c7c4bf8
|
24 |
94,792 |
0.01%
|
|
|
|
|
|- org.apache.ivy.core.module.descriptor.MDArtifact @ 0x14c7380b8
|
48 |
94,624 |
0.01%
|
|
|
|
|
|
|- org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor @ 0x14c735100
|
112 |
94,288 |
0.01%
|
|
|
|
|
|
|
|- java.util.ArrayList @ 0x14c735170
|
24 |
80,824 |
0.01%
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
References on the anonymous class ‘DefaultArtifactFactory$1’ appear to be the root problem. I hadn’t come across anonymous class leaks before, but now I think about it some more: the anonymous class being non-static, won’t be eligible for garbage collection until it’s parent object is collected. It’s referencing finals in the method, so it’ll also hold references to those.
Seems the solution is to replace the ‘Factory’ anonymous class with a static inner class.
References on the anonymous inner class:
Type|Name
|Value
----------------------------------------------------------------------------------------------------------------------------------------------------
ref |this$0
|org.gradle.api.internal.artifacts.ivyservice.ResolvedArtifactFactory @ 0x11f8f91b8
ref |val$resolver|org.gradle.api.internal.artifacts.ivyservice.ivyresolve.LazyDependencyToModuleResolver$ErrorHandlingArtifactResolver @ 0x14c74c1a0
ref |val$artifact|org.apache.ivy.core.module.descriptor.MDArtifact @ 0x14c7380b8
----------------------------------------------------------------------------------------------------------------------------------------------------
Code for reference. From GitHub today, releases branch:
public ResolvedArtifact create(ResolvedDependency owner, final Artifact artifact, final ArtifactResolver resolver) {
return new DefaultResolvedArtifact(owner, artifact, new Factory<File>() {
public File create() {
return lockingManager.useCache(String.format("download %s", artifact), new Factory<File>() {
public File create() {
DefaultBuildableArtifactResolveResult result = new DefaultBuildableArtifactResolveResult();
resolver.resolve(artifact, result);
return result.getFile();
}
});
}
});
}