gnodet opened a new pull request, #12540: URL: https://github.com/apache/maven/pull/12540
## Summary Maven 4's immutable model pattern creates excessive intermediate objects during effective model construction, contributing to ~20% more CPU time compared to Maven 3. This PR addresses the root causes through 7 optimization phases targeting the generated templates and hand-written pipeline code: ### Changes **Phase 0 — merger.vm false-positive change detection** (highest impact) - String fields: add `src != tgt` reference check before setting on builder — prevents defeating `build()` short-circuit when source and target share the same string reference (common during inheritance assembly) - Boolean/int fields: add value equality check before setting on builder - Composite fields (mult=1): add `merged != target.getField()` identity guard - List fields: add identity check for merge results **Phase 1 — model.vm computeLocations() optimization** - Replace `Stream.concat().collect(Collectors.toUnmodifiableMap())` with direct `HashMap` merge - Skip `Map.copyOf()` when old locations map is already immutable from a previous `build()` **Phase 2 — model.vm constructor: skip ImmutableCollections.copy() for base fields** - When a List/Map field comes unchanged from the base object, reuse it directly instead of passing through `ImmutableCollections.copy()` — the base's fields are already immutable from a previous `build()` **Phase 3 — DefaultModelNormalizer: avoid always-build pattern** - `mergeDuplicates()`: only create builder and call `build()` when duplicate plugins/dependencies are actually found - `injectDefaultValues()`: only build when scope injection changes something - `injectPlugin()`: skip rebuild when plugin dependencies don't need scope injection **Phase 4 — DefaultProfileInjector: guard sub-object rebuilds** - Add `newBuild != build` identity check before setting Build on Model.Builder **Phase 5 — DefaultModelBuilder: replace nested with*() chains** - Replace patterns like `model.withParent(parent.withRelativePath(x))` (2 full immutable copies) with single-builder patterns - Consolidate `withProfiles(List.of()).withParent(null)` into single builder call - Add identity check before `withPomFile()` **Phase 6 — DefaultPluginConfigurationExpander: skip unchanged rebuilds** - Check if plugin expansion actually changed anything before rebuilding Build/Model objects - Original code unconditionally called `model.withBuild(build)` even when nothing expanded **Phase 7 — ImmutableCollections.copy(Map): detect JDK unmodifiable maps** - `copy(Map)` only recognized custom `AbstractImmutableMap` as already-immutable - Now also recognizes JDK internal unmodifiable maps (`Map.of()`, `Map.copyOf()`) to avoid redundant wrapping ### Impact These changes target the ~16-20 intermediate `Model` objects created per POM during `buildEffectiveModel()` / `readEffectiveModel()`. The `build()` short-circuit (`if base != null && all fields unchanged → return base`) was being defeated by false-positive field assignments in the merger, causing unnecessary object allocation chains (Model → Build → Plugin → Dependency → ...). Profiling data (JDK 25 JFR on Apache Camel, ~1000 modules): - Maven 4 user CPU: ~36s vs Maven 3: ~30s (~20% overhead) - Primary allocation sites: `MavenMerger.merge*` → `Builder.build()` → constructor → `ImmutableCollections.copy()` + `computeLocations()` ## Test plan - [x] `mvn test -pl impl/maven-impl` — 466 tests pass, 0 failures - [x] `mvn test -pl compat/maven-model-builder` — 166 tests pass, 0 failures - [x] `mvn test -pl impl/maven-core` — 549 pass (5 pre-existing failures on master) - [ ] Full CI verification 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
