Operations 16 min read

Humongous Objects in G1 GC: Why They Fear Contiguous Space and Hurt Performance

Humongous objects in Java's G1 collector are large allocations that require a contiguous set of heap regions, bypass the young generation, inflate old‑generation usage, trigger frequent Young, Mixed or Full GCs, and can dominate CPU and pause time unless the code is refactored or the region size is tuned.

CodeOnCode
CodeOnCode
CodeOnCode
Humongous Objects in G1 GC: Why They Fear Contiguous Space and Hurt Performance

Understanding Humongous Objects in G1 GC

In G1, an object whose size is at least 50% of a region is classified as a Humongous object. The threshold is fixed and depends on G1HeapRegionSize:

G1HeapRegionSize = 4MB  → Humongous threshold = 2MB
G1HeapRegionSize = 2MB  → Humongous threshold = 1MB
G1HeapRegionSize = 1MB  → Humongous threshold = 512KB

Typical examples that become Humongous are a 3 MB byte[], a large char[], a massive JSON string, or an ArrayList whose backing array exceeds the threshold.

Humongous objects differ from ordinary objects in three ways:

Bypass young generation – they are allocated directly in the old generation and do not participate in Young GC.

Require contiguous regions – the JVM must find a sequence of free regions; if none exist, allocation fails and may trigger a Full GC.

Raise old‑generation occupancy – each allocation instantly increases old‑gen usage, potentially prompting earlier concurrent marking.

These effects explain log entries such as: Pause Young (Normal) (G1 Humongous Allocation) When a Humongous allocation cannot find enough contiguous space, the JVM first attempts a Young GC to free space; if that fails, it runs a Mixed GC, and as a last resort a Full GC. This is why Humongous allocations are a common trigger for Full GC even when the heap is far from full.

Typical Scenarios that Produce Humongous Objects

Batch export of List<Map<String, Object>> Processing huge JSON payloads

Large report queries that materialise tens of thousands of rows

Reading large files into a byte[] In these cases the problem is not overall heap size but the lack of a contiguous block large enough for the object.

When Humongous Objects Are Reclaimed

Ordinary objects move from Eden → Survivor → Old and are reclaimed by Mixed GC. Humongous objects, however, are allocated directly in Old and are reclaimed only by Mixed GC or Full GC. Since JDK 8u40 the JVM enables Eager Reclaim of Humongous Objects ( -XX:+G1EagerReclaimHumongousObjects) which checks during Young GC whether a Humongous region is unreferenced and, if so, frees it immediately. This optimization helps only for short‑lived Humongous objects; long‑lived ones still require a full or mixed collection.

Practical Tuning Steps

Inspect code for large objects and try to split them (e.g., process large arrays in chunks, use StringBuilder for big strings, stream large lists).

If splitting is impossible, consider increasing G1HeapRegionSize (e.g., -XX:G1HeapRegionSize=8M) which raises the Humongous threshold, but beware that larger regions reduce G1's flexibility and may increase pause times.

Increase overall heap size to provide more regions and reduce fragmentation.

Monitor Humongous activity with GC logs ( -Xlog:gc*:file=gc.log) and marking logs ( -Xlog:gc+marking=debug).

Case Study: Report Service Causing Full GC

A nightly report that loads 50 000 rows into a List<Map<String, Object>> creates an ArrayList whose backing array exceeds 2 MB, becoming a Humongous object. The GC log shows:

[GC pause (G1 Humongous Allocation) (young)]
[Full GC (Allocation Failure)]

After switching to pagination, using MyBatis streaming (

@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)

), and avoiding the intermediate list, the Humongous allocation disappeared and Full GC stopped occurring.

Allocation Overhead

Humongous allocation is slower than normal TLAB allocation because the JVM must:

Search the free‑region list for a sufficient contiguous block.

Mark the selected regions as starts humongous and continues humongous.

Update internal metadata.

Potentially trigger a Young or Mixed GC if space is insufficient.

High‑frequency Humongous allocations (e.g., dozens per second) can become a noticeable CPU cost, as shown by flame‑graphs where 5 % of CPU time is spent in allocation.

Impact on Concurrent Marking

Because Humongous objects reside in Old, they raise the old‑gen occupancy. When occupancy reaches the InitiatingHeapOccupancyPercent (default 45 %), G1 starts a concurrent marking cycle. Frequent Humongous allocations therefore increase the frequency of concurrent marking, consuming CPU and potentially slowing application threads.

Region Size Trade‑offs

Default G1HeapRegionSize is chosen based on total heap size (e.g., <1 GB → 1 MB, 2‑8 GB → 2 MB, 8‑32 GB → 4 MB, >32 GB → up to 32 MB). Smaller regions lower the Humongous threshold, causing more objects to be classified as Humongous, while larger regions raise the threshold but increase pause time during evacuation. There is no universally optimal size; it must match the application's object size distribution and pause‑time sensitivity.

Humongous Investigation Checklist

Confirm the problem is Humongous‑related by checking GC logs for G1 Humongous Allocation, sudden old‑gen jumps, and Full GC preceded by Humongous allocation.

Identify the code creating large objects using JFR ( -XX:StartFlightRecording=settings=profile) or async‑profiler (

./profiler.sh -e G1CollectedHeap::humongous_obj_allocate -d 60 -f flamegraph.html <pid>

).

Determine if the object can be split, reused (object pool, ThreadLocal), delayed, or moved off‑heap ( DirectByteBuffer).

If unavoidable, tune parameters: increase region size, enlarge heap, or adjust InitiatingHeapOccupancyPercent.

Continuously monitor Humongous allocation frequency, old‑gen occupancy, Full GC rate, and concurrent marking start rate.

In summary, Humongous objects are a special class of large allocations in G1 that bypass the young generation, require contiguous space, increase old‑gen pressure, and can trigger costly GC cycles. Effective mitigation starts with code‑level changes (splitting, streaming, pooling), followed by careful tuning of region size and heap parameters, and ongoing monitoring.

Humongous allocation illustration
Humongous allocation illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaPerformanceMemoryGCG1Humongous
CodeOnCode
Written by

CodeOnCode

The road is long and arduous, but keep moving to reach your goal.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.