diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index d6474dcc8c83d3..45d8a5762e23df 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -23,7 +23,8 @@ public enum GCCollectionMode { Default = 0, Forced = 1, - Optimized = 2 + Optimized = 2, + Aggressive = 3, } // !!!!!!!!!!!!!!!!!!!!!!! @@ -35,6 +36,7 @@ internal enum InternalGCCollectionMode Blocking = 0x00000002, Optimized = 0x00000004, Compacting = 0x00000008, + Aggressive = 0x00000010, } // !!!!!!!!!!!!!!!!!!!!!!! @@ -197,7 +199,7 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, throw new ArgumentOutOfRangeException(nameof(generation), SR.ArgumentOutOfRange_GenericPositive); } - if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Optimized)) + if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Aggressive)) { throw new ArgumentOutOfRangeException(nameof(mode), SR.ArgumentOutOfRange_Enum); } @@ -209,6 +211,22 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, { iInternalModes |= (int)InternalGCCollectionMode.Optimized; } + else if (mode == GCCollectionMode.Aggressive) + { + iInternalModes |= (int)InternalGCCollectionMode.Aggressive; + if (generation != MaxGeneration) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresMaxGeneration, nameof(generation)); + } + if (!blocking) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresBlocking, nameof(blocking)); + } + if (!compacting) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresCompacting, nameof(compacting)); + } + } if (compacting) iInternalModes |= (int)InternalGCCollectionMode.Compacting; diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index e42d29365f7225..a7cccb0d2d88bd 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -211,6 +211,7 @@ BOOL is_induced (gc_reason reason) (reason == reason_lowmemory) || (reason == reason_lowmemory_blocking) || (reason == reason_induced_compacting) || + (reason == reason_induced_aggressive) || (reason == reason_lowmemory_host) || (reason == reason_lowmemory_host_blocking)); } @@ -221,6 +222,7 @@ BOOL is_induced_blocking (gc_reason reason) return ((reason == reason_induced) || (reason == reason_lowmemory_blocking) || (reason == reason_induced_compacting) || + (reason == reason_induced_aggressive) || (reason == reason_lowmemory_host_blocking)); } @@ -12297,6 +12299,56 @@ void gc_heap::distribute_free_regions() { #ifdef USE_REGIONS const int kind_count = large_free_region + 1; + if (settings.reason == reason_induced_aggressive) + { +#ifdef MULTIPLE_HEAPS + for (int i = 0; i < n_heaps; i++) + { + gc_heap* hp = g_heaps[i]; +#else //MULTIPLE_HEAPS + { + gc_heap* hp = pGenGCHeap; +#endif //MULTIPLE_HEAPS + for (int kind = basic_free_region; kind < count_free_region_kinds; kind++) + { + global_regions_to_decommit[kind].transfer_regions (&hp->free_regions[kind]); + } + } + while (decommit_step()) + { + } +#ifdef MULTIPLE_HEAPS + for (int i = 0; i < n_heaps; i++) + { + gc_heap* hp = g_heaps[i]; + int hn = i; +#else //MULTIPLE_HEAPS + { + gc_heap* hp = pGenGCHeap; + int hn = 0; +#endif //MULTIPLE_HEAPS + for (int i = 0; i < total_generation_count; i++) + { + generation* generation = hp->generation_of (i); + heap_segment* region = heap_segment_rw (generation_start_segment (generation)); + while (region != nullptr) + { + uint8_t* aligned_allocated = align_on_page (heap_segment_allocated (region)); + size_t end_space = heap_segment_committed (region) - aligned_allocated; + if (end_space > 0) + { + virtual_decommit (aligned_allocated, end_space, gen_to_oh (i), hn); + heap_segment_committed (region) = aligned_allocated; + heap_segment_used (region) = min (heap_segment_used (region), heap_segment_committed (region)); + assert (heap_segment_committed (region) > heap_segment_mem (region)); + } + region = heap_segment_next_rw (region); + } + } + } + + return; + } // first step: accumulate the number of free regions and the budget over all heaps // and move huge regions to global free list @@ -30568,6 +30620,10 @@ void gc_heap::update_start_tail_regions (generation* gen, inline bool gc_heap::should_sweep_in_plan (heap_segment* region) { + if (settings.reason == reason_induced_aggressive) + { + return false; + } bool sip_p = false; int gen_num = get_region_gen_num (region); int new_gen_num = get_plan_gen_num (gen_num); @@ -40218,6 +40274,13 @@ BOOL gc_heap::decide_on_compacting (int condemned_gen_number, get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_induced_compacting); } + if (settings.reason == reason_induced_aggressive) + { + dprintf (2, ("aggressive compacting GC")); + should_compact = TRUE; + get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_aggressive_compacting); + } + if (settings.reason == reason_pm_full_gc) { assert (condemned_gen_number == max_generation); @@ -45233,7 +45296,11 @@ GCHeap::GarbageCollectTry (int generation, BOOL low_memory_p, int mode) if (reason == reason_induced) { - if (mode & collection_compacting) + if (mode & collection_aggressive) + { + reason = reason_induced_aggressive; + } + else if (mode & collection_compacting) { reason = reason_induced_compacting; } diff --git a/src/coreclr/gc/gc.h b/src/coreclr/gc/gc.h index a47f84089bc431..b3d771bd34fafd 100644 --- a/src/coreclr/gc/gc.h +++ b/src/coreclr/gc/gc.h @@ -74,6 +74,7 @@ enum gc_reason reason_bgc_tuning_soh = 14, reason_bgc_tuning_loh = 15, reason_bgc_stepping = 16, + reason_induced_aggressive = 17, reason_max }; diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index a1edb39e59fcb3..669be562ca05cb 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -275,7 +275,8 @@ enum collection_mode collection_non_blocking = 0x00000001, collection_blocking = 0x00000002, collection_optimized = 0x00000004, - collection_compacting = 0x00000008 + collection_compacting = 0x00000008, + collection_aggressive = 0x00000010 #ifdef STRESS_HEAP , collection_gcstress = 0x80000000 #endif // STRESS_HEAP diff --git a/src/coreclr/gc/gcrecord.h b/src/coreclr/gc/gcrecord.h index 68d0f4dfabe210..7df8d0aca03d77 100644 --- a/src/coreclr/gc/gcrecord.h +++ b/src/coreclr/gc/gcrecord.h @@ -248,7 +248,8 @@ enum gc_heap_compact_reason compact_high_mem_frag = 8, compact_vhigh_mem_frag = 9, compact_no_gc_mode = 10, - max_compact_reasons_count = 11 + compact_aggressive_compacting = 11, + max_compact_reasons_count = 12 }; #ifndef DACCESS_COMPILE @@ -264,7 +265,8 @@ static BOOL gc_heap_compact_reason_mandatory_p[] = FALSE, //compact_high_mem_load = 7, TRUE, //compact_high_mem_frag = 8, TRUE, //compact_vhigh_mem_frag = 9, - TRUE //compact_no_gc_mode = 10 + TRUE, //compact_no_gc_mode = 10, + TRUE //compact_aggressive_compacting = 11 }; static BOOL gc_expand_mechanism_mandatory_p[] = diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index b4d59af8f3655b..8ac6a38324d56e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -22,7 +22,8 @@ public enum GCCollectionMode { Default = 0, Forced = 1, - Optimized = 2 + Optimized = 2, + Aggressive = 3, } public enum GCNotificationStatus @@ -40,6 +41,7 @@ internal enum InternalGCCollectionMode Blocking = 0x00000002, Optimized = 0x00000004, Compacting = 0x00000008, + Aggressive = 0x00000010 } internal enum StartNoGCRegionStatus @@ -124,7 +126,7 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, throw new ArgumentOutOfRangeException(nameof(generation), SR.ArgumentOutOfRange_GenericPositive); } - if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Optimized)) + if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Aggressive)) { throw new ArgumentOutOfRangeException(nameof(mode), SR.ArgumentOutOfRange_Enum); } @@ -135,6 +137,22 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, { iInternalModes |= (int)InternalGCCollectionMode.Optimized; } + else if (mode == GCCollectionMode.Aggressive) + { + iInternalModes |= (int)InternalGCCollectionMode.Aggressive; + if (generation != MaxGeneration) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresMaxGeneration, nameof(generation)); + } + if (!blocking) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresBlocking, nameof(blocking)); + } + if (!compacting) + { + throw new ArgumentException(SR.Argument_AggressiveGCRequiresCompacting, nameof(compacting)); + } + } if (compacting) { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index b1deeb8c264849..98c61f7eb80539 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3529,6 +3529,15 @@ The method was called with a null array argument. + + AggressiveGC requires setting the generation parameter to MaxGeneration + + + AggressiveGC requires setting the blocking parameter to true. + + + AggressiveGC requires setting the compacting parameter to true. + Abstract methods cannot be prepared. diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index bcfaa53f8fa3fa..5cb7053223fd72 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2562,12 +2562,23 @@ public static void WaitForPendingFinalizers() { } /// The total amount of time paused in GC since the beginning of the process. public static TimeSpan GetTotalPauseDuration() { return TimeSpan.Zero; } } + + /// Specifies the behavior for a forced garbage collection. public enum GCCollectionMode { + /// The default setting for this enumeration, which is currently . Default = 0, + + /// Forces the garbage collection to occur immediately. Forced = 1, + + /// Allows the garbage collector to determine whether the current time is optimal to reclaim objects. Optimized = 2, + + /// Requests that the garbage collector decommit as much memory as possible. + Aggressive = 3, } + public readonly partial struct GCGenerationInfo { private readonly int _dummyPrimitive; diff --git a/src/libraries/System.Runtime/tests/System/GCTests.cs b/src/libraries/System.Runtime/tests/System/GCTests.cs index 4faaa1d94e6ea4..31663d2276468b 100644 --- a/src/libraries/System.Runtime/tests/System/GCTests.cs +++ b/src/libraries/System.Runtime/tests/System/GCTests.cs @@ -69,7 +69,7 @@ public static void Collect_NegativeGenerationCount_ThrowsArgumentOutOfRangeExcep [Theory] [InlineData(GCCollectionMode.Default - 1)] - [InlineData(GCCollectionMode.Optimized + 1)] + [InlineData(GCCollectionMode.Aggressive + 1)] public static void Collection_InvalidCollectionMode_ThrowsArgumentOutOfRangeException(GCCollectionMode mode) { AssertExtensions.Throws("mode", null, () => GC.Collect(2, mode)); diff --git a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs index b5bc27542b5fef..681a8cc03bbaa4 100644 --- a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs @@ -11,7 +11,8 @@ public enum GCCollectionMode { Default = 0, Forced = 1, - Optimized = 2 + Optimized = 2, + Aggressive = 3, } public enum GCNotificationStatus @@ -91,7 +92,7 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, { if (generation < 0) throw new ArgumentOutOfRangeException(nameof(generation), "generation", SR.ArgumentOutOfRange_GenericPositive); - if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Optimized)) + if ((mode < GCCollectionMode.Default) || (mode > GCCollectionMode.Aggressive)) throw new ArgumentOutOfRangeException(nameof(mode), SR.ArgumentOutOfRange_Enum); InternalCollect(generation); diff --git a/src/tests/GC/API/GC/Collect_Aggressive.cs b/src/tests/GC/API/GC/Collect_Aggressive.cs new file mode 100644 index 00000000000000..3d04a8c93d6830 --- /dev/null +++ b/src/tests/GC/API/GC/Collect_Aggressive.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +public class AggressiveCollect +{ + public static int Main(string[] args ) + { + long before = CreateGarbage(); + GC.Collect(2, GCCollectionMode.Aggressive, blocking: true, compacting: true); + long after = GC.GetGCMemoryInfo().TotalCommittedBytes; + long reclaimed = before - after; + long reclaimedAtLeast = 2000 * 4000; + if (reclaimed < reclaimedAtLeast) + { + // If we reach this case, the aggressive GC is not releasing as much memory as + // we wished, something is wrong. + return 101; + } + else + { + // Doing some extra allocation (and also trigger GC indirectly) here + // should be just fine. + for (int i = 0; i < 10; i++) + { + CreateGarbage(); + } + return 100; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static long CreateGarbage() + { + byte[][] smallGarbage = new byte[2000][]; + + // This will force us to use more than one region in the small object heap + for (int i = 0; i < 2000; i++) + { + // It will roughly span one page + smallGarbage[i] = new byte[4000]; + } + + // This will force us to use more than one region in the large object heap + byte[] largeGarbage = new byte[33 * 1024 * 1024]; + + // This will force us to use more than one region in the pin object heap + byte[] pinnedGarbage = GC.AllocateArray(33 * 1024 * 1024, /* pinned = */true); + + GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true); + long committed = GC.GetGCMemoryInfo().TotalCommittedBytes; + + GC.KeepAlive(smallGarbage); + GC.KeepAlive(largeGarbage); + GC.KeepAlive(pinnedGarbage); + + return committed; + } +} diff --git a/src/tests/GC/API/GC/Collect_Aggressive.csproj b/src/tests/GC/API/GC/Collect_Aggressive.csproj new file mode 100644 index 00000000000000..306065392ef021 --- /dev/null +++ b/src/tests/GC/API/GC/Collect_Aggressive.csproj @@ -0,0 +1,15 @@ + + + Exe + + true + 0 + + + + PdbOnly + + + + + diff --git a/src/tests/GC/API/GC/Collect_neg.cs b/src/tests/GC/API/GC/Collect_neg.cs index 06c47ea7adf0e9..979d53016b33bf 100644 --- a/src/tests/GC/API/GC/Collect_neg.cs +++ b/src/tests/GC/API/GC/Collect_neg.cs @@ -8,7 +8,7 @@ public class NegCollect public static int Main() { bool retVal = true; - GCCollectionMode[] invalidInputs = { (GCCollectionMode)(GCCollectionMode.Default - 1), (GCCollectionMode)(GCCollectionMode.Optimized + 1) }; + GCCollectionMode[] invalidInputs = { (GCCollectionMode)(GCCollectionMode.Default - 1), (GCCollectionMode)(GCCollectionMode.Aggressive + 1) }; for (int i = 0; i < invalidInputs.Length; i++) {