Skip to content

Commit acc18b3

Browse files
committed
Improve Maven cache architecture for better memory efficiency and performance
This comprehensive enhancement to Maven's caching system addresses memory issues and significantly improves performance through several key improvements: ## Key Features: - Enhanced DefaultRequestCache with configurable reference types and CSS-like selectors - Pluggable ModelObjectPool service architecture with configurable object types - Comprehensive cache statistics with eviction tracking - Improved InputLocation and InputSource with ImmutableCollections - Custom equality strategy for Dependency pooling - Enhanced parent request matching with interface checking - Configurable cache statistics display ## Performance Results: - Maven 3: Requires -Xmx1536m, runs in 45 seconds - Maven 4.0.0-rc-4: Runs with -Xmx1024m in 2'32" (cannot run with -Xmx512m) - Maven 4.0.0-rc-4 with -Xmx1536m: 2'5" - Maven 4.0.0-rc-4 + maven3Personality with -Xmx1536m: 1'14" - Maven 4 + this PR: Runs with -Xmx512m in 3'42" (more memory does not help) - Maven 4 + this PR + maven3Personality: Runs with -Xmx512m in 1'0" ## Memory Improvements: - Reduced minimum memory requirement from 1024m to 512m - Eliminated memory scaling issues - additional memory beyond 512m provides no benefit - Significant reduction in memory pressure through improved caching strategies This PR definitively solves memory problems while maintaining or improving performance.
1 parent 1a681ec commit acc18b3

File tree

52 files changed

+5285
-641
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5285
-641
lines changed

api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,5 +622,66 @@ public final class Constants {
622622
*/
623623
public static final String MAVEN_LOGGER_LOG_PREFIX = MAVEN_LOGGER_PREFIX + "log.";
624624

625+
/**
626+
* User property key for cache configuration.
627+
*
628+
* @since 4.1.0
629+
*/
630+
public static final String MAVEN_CACHE_CONFIG_PROPERTY = "maven.cache.config";
631+
632+
/**
633+
* User property to enable cache statistics display at the end of the build.
634+
* When set to true, detailed cache statistics including hit/miss ratios,
635+
* request type breakdowns, and retention policy effectiveness will be displayed
636+
* when the build completes.
637+
*
638+
* @since 4.1.0
639+
*/
640+
@Config(type = "java.lang.Boolean", defaultValue = "false")
641+
public static final String MAVEN_CACHE_STATS = "maven.cache.stats";
642+
643+
/**
644+
* User property to configure separate reference types for cache keys and values.
645+
* Format: "key:value" where key and value can be NONE, SOFT, WEAK, or HARD.
646+
* Examples:
647+
* - "HARD:SOFT" - Keep keys strongly referenced, allow values to be garbage collected under memory pressure
648+
* - "WEAK:WEAK" - Allow both keys and values to be garbage collected aggressively
649+
* - "SOFT:HARD" - Allow keys to be GC'd under memory pressure, keep values strongly referenced
650+
*
651+
* This enables fine-grained analysis of cache misses caused by key vs value evictions.
652+
*
653+
* @since 4.1.0
654+
*/
655+
public static final String MAVEN_CACHE_KEY_VALUE_REFS = "maven.cache.keyValueRefs";
656+
657+
/**
658+
* User property key for configuring which object types are pooled by ModelObjectProcessor.
659+
* Value should be a comma-separated list of simple class names (e.g., "Dependency,Plugin,Build").
660+
* Default is "Dependency" for backward compatibility.
661+
*
662+
* @since 4.1.0
663+
*/
664+
@Config(defaultValue = "Dependency")
665+
public static final String MAVEN_MODEL_PROCESSOR_POOLED_TYPES = "maven.model.processor.pooledTypes";
666+
667+
/**
668+
* User property key for configuring the default reference type used by ModelObjectProcessor.
669+
* Valid values are: "SOFT", "HARD", "WEAK", "NONE".
670+
* Default is "HARD" for optimal performance.
671+
*
672+
* @since 4.1.0
673+
*/
674+
@Config(defaultValue = "HARD")
675+
public static final String MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE = "maven.model.processor.referenceType";
676+
677+
/**
678+
* User property key prefix for configuring per-object-type reference types.
679+
* Format: maven.model.processor.referenceType.{ClassName} = {ReferenceType}
680+
* Example: maven.model.processor.referenceType.Dependency = SOFT
681+
*
682+
* @since 4.1.0
683+
*/
684+
public static final String MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE_PREFIX = "maven.model.processor.referenceType.";
685+
625686
private Constants() {}
626687
}

api/maven-api-model/src/main/java/org/apache/maven/api/model/InputLocation.java

Lines changed: 151 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020

2121
import java.io.Serializable;
2222
import java.util.Collection;
23-
import java.util.Collections;
2423
import java.util.LinkedHashMap;
2524
import java.util.Map;
25+
import java.util.Objects;
2626

2727
/**
2828
* Represents the location of an element within a model source file.
@@ -40,11 +40,15 @@ public class InputLocation implements Serializable, InputLocationTracker {
4040
private final Map<Object, InputLocation> locations;
4141
private final InputLocation importedFrom;
4242

43+
private volatile int hashCode = 0; // Cached hashCode for performance
44+
45+
private static final InputLocation EMPTY = new InputLocation(-1, -1);
46+
4347
public InputLocation(InputSource source) {
4448
this.lineNumber = -1;
4549
this.columnNumber = -1;
4650
this.source = source;
47-
this.locations = Collections.singletonMap(0, this);
51+
this.locations = ImmutableCollections.singletonMap(0, this);
4852
this.importedFrom = null;
4953
}
5054

@@ -60,8 +64,9 @@ public InputLocation(int lineNumber, int columnNumber, InputSource source, Objec
6064
this.lineNumber = lineNumber;
6165
this.columnNumber = columnNumber;
6266
this.source = source;
63-
this.locations =
64-
selfLocationKey != null ? Collections.singletonMap(selfLocationKey, this) : Collections.emptyMap();
67+
this.locations = selfLocationKey != null
68+
? ImmutableCollections.singletonMap(selfLocationKey, this)
69+
: ImmutableCollections.emptyMap();
6570
this.importedFrom = null;
6671
}
6772

@@ -73,12 +78,75 @@ public InputLocation(int lineNumber, int columnNumber, InputSource source, Map<O
7378
this.importedFrom = null;
7479
}
7580

76-
public InputLocation(InputLocation original) {
77-
this.lineNumber = original.lineNumber;
78-
this.columnNumber = original.columnNumber;
79-
this.source = original.source;
80-
this.locations = original.locations;
81-
this.importedFrom = original.importedFrom;
81+
// Factory methods
82+
83+
public static InputLocation of() {
84+
return EMPTY;
85+
}
86+
87+
/**
88+
* Creates a new InputLocation with the specified source.
89+
* The created instance is processed through ModelObjectProcessor for optimization.
90+
*
91+
* @param source the input source
92+
* @return a new InputLocation instance
93+
*/
94+
public static InputLocation of(InputSource source) {
95+
return ModelObjectProcessor.processObject(new InputLocation(source));
96+
}
97+
98+
/**
99+
* Creates a new InputLocation with the specified line and column numbers.
100+
* The created instance is processed through ModelObjectProcessor for optimization.
101+
*
102+
* @param lineNumber the line number
103+
* @param columnNumber the column number
104+
* @return a new InputLocation instance
105+
*/
106+
public static InputLocation of(int lineNumber, int columnNumber) {
107+
return ModelObjectProcessor.processObject(new InputLocation(lineNumber, columnNumber));
108+
}
109+
110+
/**
111+
* Creates a new InputLocation with the specified line number, column number, and source.
112+
* The created instance is processed through ModelObjectProcessor for optimization.
113+
*
114+
* @param lineNumber the line number
115+
* @param columnNumber the column number
116+
* @param source the input source
117+
* @return a new InputLocation instance
118+
*/
119+
public static InputLocation of(int lineNumber, int columnNumber, InputSource source) {
120+
return ModelObjectProcessor.processObject(new InputLocation(lineNumber, columnNumber, source));
121+
}
122+
123+
/**
124+
* Creates a new InputLocation with the specified line number, column number, source, and self location key.
125+
* The created instance is processed through ModelObjectProcessor for optimization.
126+
*
127+
* @param lineNumber the line number
128+
* @param columnNumber the column number
129+
* @param source the input source
130+
* @param selfLocationKey the self location key
131+
* @return a new InputLocation instance
132+
*/
133+
public static InputLocation of(int lineNumber, int columnNumber, InputSource source, Object selfLocationKey) {
134+
return ModelObjectProcessor.processObject(new InputLocation(lineNumber, columnNumber, source, selfLocationKey));
135+
}
136+
137+
/**
138+
* Creates a new InputLocation with the specified line number, column number, source, and locations map.
139+
* The created instance is processed through ModelObjectProcessor for optimization.
140+
*
141+
* @param lineNumber the line number
142+
* @param columnNumber the column number
143+
* @param source the input source
144+
* @param locations the locations map
145+
* @return a new InputLocation instance
146+
*/
147+
public static InputLocation of(
148+
int lineNumber, int columnNumber, InputSource source, Map<Object, InputLocation> locations) {
149+
return ModelObjectProcessor.processObject(new InputLocation(lineNumber, columnNumber, source, locations));
82150
}
83151

84152
public int getLineNumber() {
@@ -184,21 +252,83 @@ public static InputLocation merge(InputLocation target, InputLocation source, Co
184252
return new InputLocation(-1, -1, InputSource.merge(source.getSource(), target.getSource()), locations);
185253
} // -- InputLocation merge( InputLocation, InputLocation, java.util.Collection )
186254

255+
@Override
256+
public boolean equals(Object o) {
257+
if (this == o) {
258+
return true;
259+
}
260+
if (o == null || getClass() != o.getClass()) {
261+
return false;
262+
}
263+
InputLocation that = (InputLocation) o;
264+
return lineNumber == that.lineNumber
265+
&& columnNumber == that.columnNumber
266+
&& Objects.equals(source, that.source)
267+
&& safeLocationsEquals(this, locations, that, that.locations)
268+
&& Objects.equals(importedFrom, that.importedFrom);
269+
}
270+
187271
/**
188-
* Class StringFormatter.
189-
*
190-
* @version $Revision$ $Date$
272+
* Safely compares two locations maps, treating self-references as equal.
191273
*/
192-
public interface StringFormatter {
274+
private static boolean safeLocationsEquals(
275+
InputLocation this1,
276+
Map<Object, InputLocation> map1,
277+
InputLocation this2,
278+
Map<Object, InputLocation> map2) {
279+
if (map1 == map2) {
280+
return true;
281+
}
282+
if (map1 == null || map2 == null) {
283+
return false;
284+
}
285+
if (map1.size() != map2.size()) {
286+
return false;
287+
}
193288

194-
// -----------/
195-
// - Methods -/
196-
// -----------/
289+
for (Map.Entry<Object, InputLocation> entry1 : map1.entrySet()) {
290+
Object key = entry1.getKey();
291+
InputLocation value1 = entry1.getValue();
292+
InputLocation value2 = map2.get(key);
197293

198-
/**
199-
* Method toString.
200-
*/
201-
String toString(InputLocation location);
294+
if (value1 == this1) {
295+
if (value2 == this2) {
296+
continue;
297+
}
298+
return false;
299+
} else {
300+
if (Objects.equals(value1, value2)) {
301+
continue;
302+
}
303+
return false;
304+
}
305+
}
306+
307+
return true;
308+
}
309+
310+
@Override
311+
public int hashCode() {
312+
int result = hashCode;
313+
if (result == 0) {
314+
result = Objects.hash(lineNumber, columnNumber, source, safeHash(locations), importedFrom);
315+
hashCode = result;
316+
}
317+
return result;
318+
}
319+
320+
public int safeHash(Map<Object, InputLocation> locations) {
321+
if (locations == null) {
322+
return 0;
323+
}
324+
int result = 1;
325+
for (Map.Entry<Object, InputLocation> entry : locations.entrySet()) {
326+
result = 31 * result + Objects.hashCode(entry.getKey());
327+
if (entry.getValue() != this) {
328+
result = 31 * result + Objects.hashCode(entry.getValue());
329+
}
330+
}
331+
return result;
202332
}
203333

204334
@Override

api/maven-api-model/src/main/java/org/apache/maven/api/model/InputSource.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public class InputSource implements Serializable {
4141
private final List<InputSource> inputs;
4242
private final InputLocation importedFrom;
4343

44+
private volatile int hashCode = 0; // Cached hashCode for performance
45+
4446
public InputSource(String modelId, String location) {
4547
this(modelId, location, null);
4648
}
@@ -59,6 +61,44 @@ public InputSource(Collection<InputSource> inputs) {
5961
this.importedFrom = null;
6062
}
6163

64+
// Factory methods
65+
66+
/**
67+
* Creates a new InputSource with the specified model ID and location.
68+
* The created instance is processed through ModelObjectProcessor for optimization.
69+
*
70+
* @param modelId the model ID
71+
* @param location the location
72+
* @return a new InputSource instance
73+
*/
74+
public static InputSource of(String modelId, String location) {
75+
return ModelObjectProcessor.processObject(new InputSource(modelId, location));
76+
}
77+
78+
/**
79+
* Creates a new InputSource with the specified model ID, location, and imported from location.
80+
* The created instance is processed through ModelObjectProcessor for optimization.
81+
*
82+
* @param modelId the model ID
83+
* @param location the location
84+
* @param importedFrom the imported from location
85+
* @return a new InputSource instance
86+
*/
87+
public static InputSource of(String modelId, String location, InputLocation importedFrom) {
88+
return ModelObjectProcessor.processObject(new InputSource(modelId, location, importedFrom));
89+
}
90+
91+
/**
92+
* Creates a new InputSource from a collection of input sources.
93+
* The created instance is processed through ModelObjectProcessor for optimization.
94+
*
95+
* @param inputs the collection of input sources
96+
* @return a new InputSource instance
97+
*/
98+
public static InputSource of(Collection<InputSource> inputs) {
99+
return ModelObjectProcessor.processObject(new InputSource(inputs));
100+
}
101+
62102
/**
63103
* Get the path/URL of the POM or {@code null} if unknown.
64104
*
@@ -99,12 +139,18 @@ public boolean equals(Object o) {
99139
InputSource that = (InputSource) o;
100140
return Objects.equals(modelId, that.modelId)
101141
&& Objects.equals(location, that.location)
102-
&& Objects.equals(inputs, that.inputs);
142+
&& Objects.equals(inputs, that.inputs)
143+
&& Objects.equals(importedFrom, that.importedFrom);
103144
}
104145

105146
@Override
106147
public int hashCode() {
107-
return Objects.hash(modelId, location, inputs);
148+
int result = hashCode;
149+
if (result == 0) {
150+
result = Objects.hash(modelId, location, inputs, importedFrom);
151+
hashCode = result;
152+
}
153+
return result;
108154
}
109155

110156
Stream<InputSource> sources() {
@@ -120,6 +166,7 @@ public String toString() {
120166
}
121167

122168
public static InputSource merge(InputSource src1, InputSource src2) {
123-
return new InputSource(Stream.concat(src1.sources(), src2.sources()).collect(Collectors.toSet()));
169+
return new InputSource(
170+
Stream.concat(src1.sources(), src2.sources()).distinct().toList());
124171
}
125172
}

0 commit comments

Comments
 (0)