You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/01 09:25:04 UTC
(maven) branch optimize-model-builder-pipeline-fix updated: Add withImportedFrom() copy method and replace CopyOnWriteArraySet
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch optimize-model-builder-pipeline-fix
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/optimize-model-builder-pipeline-fix by this push:
new d30efb592e Add withImportedFrom() copy method and replace CopyOnWriteArraySet
d30efb592e is described below
commit d30efb592eb4acf333842c14010c029c7432cfa8
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Sat Aug 1 11:24:54 2026 +0200
Add withImportedFrom() copy method and replace CopyOnWriteArraySet
JFR profiling identified two hotspots:
1. updateWithImportedFrom (35% of CPU): Rebuilt entire Dependency objects
through Builder+forceCopy+build+interning just to set the importedFrom
metadata field. Added a lightweight withImportedFrom(InputLocation) method
and private copy constructor to model.vm that shares all model fields by
reference and bypasses Builder wrapping and object pool interning.
Result: 88% reduction in updateWithImportedFrom CPU samples.
2. CopyOnWriteArraySet (6.6% of CPU): ConsumerPomArtifactTransformer used
CopyOnWriteArraySet<Path> for deferDeleteFile(), causing O(n) linear
scan + array copy per add. With 4383 modules this is O(n^2). Replaced
with ConcurrentHashMap.newKeySet() for O(1) amortized add.
Result: 100% eliminated from CPU profile.
Overall: 32% reduction in CPU samples (1028 -> 701) per JFR comparison.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../impl/ConsumerPomArtifactTransformer.java | 4 +--
.../model/DefaultDependencyManagementImporter.java | 10 +++----
src/mdo/model.vm | 32 ++++++++++++++++++++++
3 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
index f32c962886..2c15edde61 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/ConsumerPomArtifactTransformer.java
@@ -33,7 +33,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.api.feature.Features;
import org.apache.maven.api.model.Model;
@@ -62,7 +62,7 @@ class ConsumerPomArtifactTransformer extends TransformerSupport {
private static final String BUILD_POM_CLASSIFIER = "build";
- private final Set<Path> toDelete = new CopyOnWriteArraySet<>();
+ private final Set<Path> toDelete = ConcurrentHashMap.newKeySet();
private final PomBuilder builder;
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
index 828f856898..8a1858abe6 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultDependencyManagementImporter.java
@@ -167,11 +167,9 @@ static Dependency updateWithImportedFrom(Dependency dependency, DependencyManage
if (dependencySource == null
|| bomSource == null
|| Objects.equals(dependencySource.getModelId(), bomSource.getModelId())) {
- // Use forceCopy=true since we only set importedFrom (no field changes that would
- // trigger copy-on-write), and build immediately as we need the immutable result.
- return Dependency.newBuilder(dependency, true)
- .importedFrom(bomLocation)
- .build();
+ // Use withImportedFrom() — a lightweight copy that shares all model fields
+ // by reference and bypasses Builder wrapping and object pool interning.
+ return dependency.withImportedFrom(bomLocation);
}
while (dependencySource.getImportedFrom() != null) {
@@ -187,6 +185,6 @@ static Dependency updateWithImportedFrom(Dependency dependency, DependencyManage
// We modify the input location that is used for the whole file.
// This is likely correct because the POM hierarchy applies to the whole POM, not just one dependency.
- return Dependency.newBuilder(dependency, true).importedFrom(bomLocation).build();
+ return dependency.withImportedFrom(bomLocation);
}
}
diff --git a/src/mdo/model.vm b/src/mdo/model.vm
index a034c33e55..9afddcf92d 100644
--- a/src/mdo/model.vm
+++ b/src/mdo/model.vm
@@ -318,6 +318,38 @@ public class ${class.name}
return importedFrom;
}
+ /**
+ * Creates a copy of this object with a different {@code importedFrom} value.
+ * This is a lightweight operation that bypasses the Builder and object pool
+ * interning, suitable for metadata-only changes that don't affect model field
+ * values (all model fields are shared by reference with the original).
+ *
+ * @param importedFrom the new imported-from location
+ * @return a new instance with the updated importedFrom, or {@code this} if unchanged
+ */
+ @Nonnull
+ public ${class.name} withImportedFrom(InputLocation importedFrom) {
+ return this.importedFrom == importedFrom ? this : new ${class.name}(this, importedFrom);
+ }
+
+ /**
+ * Copy constructor for {@link #withImportedFrom(InputLocation)}.
+ * Copies all fields by reference (they are immutable) and sets
+ * a new {@code importedFrom}. Bypasses Builder and object pool.
+ */
+ private ${class.name}(${class.name} base, InputLocation importedFrom) {
+ #if ( $class == $root )
+ this.namespaceUri = base.namespaceUri;
+ this.modelEncoding = base.modelEncoding;
+ #end
+ #foreach ( $field in $class.getFields($version) )
+ this.${field.name} = base.${field.name};
+ #end
+ this.locations = base.locations;
+ this.locationsHashCode = base.locationsHashCode;
+ this.importedFrom = importedFrom;
+ }
+
#end
/**
* Creates a new builder with this object as the basis.