From 173d14b62c1aa4dd6dbf9410aa6ddbc23127c160 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:40:35 +0000 Subject: [PATCH 1/3] Initial plan From 22c73836e0807170d8c3167fa71162fa830082c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:46:12 +0000 Subject: [PATCH 2/3] Reduce heap usage threshold from 100MB to 10MB in ConcurrentJarCreator The previous threshold of 100000000 (100MB) divided by number of threads was causing OutOfMemoryError issues when creating zip archives, particularly in environments with limited heap space like CI systems. The ByteArrayOutputStream used internally can grow up to 2x the threshold before switching to disk-based storage, leading to excessive memory consumption. Reducing to 10000000 (10MB) reduces memory pressure while still maintaining reasonable performance. For typical builds with 4 threads, this means: - Before: 25MB per stream (potentially 50MB+ with buffer doubling) - After: 2.5MB per stream (potentially 5MB+ with buffer doubling) This change addresses the heap space errors reported in apache/maven CI builds. Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- .../org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java b/src/main/java/org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java index e431efe7..18c6ae09 100644 --- a/src/main/java/org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java +++ b/src/main/java/org/codehaus/plexus/archiver/zip/ConcurrentJarCreator.java @@ -112,7 +112,7 @@ public ConcurrentJarCreator(int nThreads) throws IOException { */ public ConcurrentJarCreator(boolean compressAddedZips, int nThreads) throws IOException { this.compressAddedZips = compressAddedZips; - ScatterGatherBackingStoreSupplier defaultSupplier = new DeferredSupplier(100000000 / nThreads); + ScatterGatherBackingStoreSupplier defaultSupplier = new DeferredSupplier(10000000 / nThreads); metaInfDir = createDeferred(defaultSupplier); manifest = createDeferred(defaultSupplier); directories = createDeferred(defaultSupplier); From fe671483469a7e1c2785b6761599224fdf2ae6bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:48:23 +0000 Subject: [PATCH 3/3] Add maximum buffer size cap to ByteArrayOutputStream Cap individual buffer allocations to 16MB to prevent excessive memory usage. The previous implementation allowed buffers to double indefinitely, which could lead to very large single allocations (32MB, 64MB, 128MB, etc.) that contribute to heap exhaustion. With this cap, when more than 16MB of data needs to be stored, the ByteArrayOutputStream will create multiple 16MB buffers instead of one giant buffer. This spreads the memory allocation across multiple smaller chunks and prevents heap fragmentation issues. Combined with the reduced threshold in ConcurrentJarCreator, this provides defense-in-depth against OutOfMemoryError during zip archive creation. Co-authored-by: slachiewicz <6705942+slachiewicz@users.noreply.github.com> --- .../plexus/archiver/zip/ByteArrayOutputStream.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/codehaus/plexus/archiver/zip/ByteArrayOutputStream.java b/src/main/java/org/codehaus/plexus/archiver/zip/ByteArrayOutputStream.java index 41a26f0c..7a6bc952 100644 --- a/src/main/java/org/codehaus/plexus/archiver/zip/ByteArrayOutputStream.java +++ b/src/main/java/org/codehaus/plexus/archiver/zip/ByteArrayOutputStream.java @@ -57,6 +57,12 @@ public class ByteArrayOutputStream extends OutputStream { */ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + /** + * Maximum size of a single buffer (16MB) to prevent excessive memory allocation. + * When buffers need to grow beyond this size, additional buffers are created instead. + */ + private static final int MAX_BUFFER_SIZE = 16 * 1024 * 1024; + /** * The list of buffers, which grows and never reduces. */ @@ -136,6 +142,9 @@ private void needNewBuffer(final int newcount) { filledBufferSum += currentBuffer.length; } + // Cap the buffer size to prevent excessive memory allocation + newBufferSize = Math.min(newBufferSize, MAX_BUFFER_SIZE); + currentBufferIndex++; currentBuffer = new byte[newBufferSize]; buffers.add(currentBuffer);