Files
Matt Valentine-House 01fe91202c Replace GC.stat scope: with a process: keyword
Recently we added some keys to GC.stat in order to measure the GC
performance of Ractor local GC and the associated global GC.

This commit refactors some of those terms: "global" was used in two
places with different meanings which could get confusing.

When it was passed to GC.stat as scope: :global it meant "Give me
aggregate stats for all the GCs running in this process".

Whereas inside the output of GC.stat, global referred to the number of
"global" GCs that are run. That is: stop-the-world GCs that run on
every ractor heap in the process.

Also the global_gc_count that GC.stat reports for a Ractor ignored
global GCs that other Ractors started, even though they swept its heap.

In this commit we fix this confusing behaviour, and make the GC.stat
output more consistent.

We've replaced the scope: parameter to GC.stat with the boolean
process: keyword arg. The default is process: false which means
GC.stat will return stats for the Ractor that executes the code (the
main Ractor if no other Ractors have been created). process: true will
return aggregate stats over all the Ractors in the process (including
dead ones).

Inside GC.stat(process: false), count and global_gc_count now
include every global GC that has swept that Ractor's heap. So Ractor
local gc count == count - global_gc_count.

heap_marked_slots now counts only the marked objects in that Ractor's
own heap. Before, the Ractor that started a global GC counted the
marked objects of every heap, and the other Ractors reported 0.

We also provide a new key global_gc_invocation_count that is the
number of global GCs that have been triggered by the Ractor in question.

We've also moved some keys related to the global page pool out of the
default GC.stat output and into GC.stat(process: true), because they
are process-wide and not Ractor specific.
2026-10-23 17:05:54 +01:00

715 lines
25 KiB
Ruby

# for gc.c
# The \GC module provides an interface to Ruby's mark-and-sweep garbage collection mechanism.
#
# Some of the underlying methods are also available via the ObjectSpace module.
#
# You may obtain information about the operation of the \GC through GC::Profiler.
module GC
# Initiates garbage collection, even if explicitly disabled by GC.disable.
#
# Keyword arguments:
#
# - +full_mark+:
# a boolean value specifying whether to perform a major garbage collection cycle:
#
# - +true+: initiates a major garbage collection cycle,
# meaning all objects (old and new) are marked. When another Ractor's
# object space exists (a live Ractor, or a terminated one not yet
# absorbed), or one was absorbed since the last global collection, and
# +global+ is +true+ (the default), the cycle is a global collection
# instead; see +global+.
# - +false+: initiates a minor garbage collection cycle,
# meaning only young objects are marked.
#
# - +immediate_mark+:
# a boolean value specifying whether marking is performed incrementally:
#
# - +true+: marking is completed before the method returns.
# - +false+: marking is performed in parts,
# interleaved with program execution both before the method returns and afterward;
# therefore marking may not be completed before the return.
# Note that if +full_mark+ is +false+, marking is always immediate,
# regardless of the value of +immediate_mark+.
#
# - +immediate_sweep+:
# a boolean value specifying whether sweeping is deferred (lazy sweep):
#
# - +true+: sweeping is completed before the method returns.
# - +false+: sweeping is performed in parts,
# interleaved with program execution both before the method returns and afterward;
# therefore sweeping may not be completed before the return.
#
# - +global+:
# a boolean value specifying, when another Ractor's object space exists, whether to
# collect every Ractor's heap together in a single stop-the-world mark/sweep cycle:
#
# - +true+: with another Ractor's object space present (see +full_mark+) it runs a
# stop-the-world cycle and reclaims shareable and cross-Ractor garbage. This option,
# although it defaults to +true+, is ignored unless +full_mark+, +immediate_mark+ and
# +immediate_sweep+ are all +true+. With a single object space it has no effect and
# the other options apply as usual. This option is also ignored unless you're using
# the default GC.
# - +false+: collect only the calling Ractor's own objects. Shareable objects and cross-ractor
# garbage are not swept.
#
# GC.stat counts a local major collection under +:major_gc_count+ and a local
# minor collection under +:minor_gc_count+. A global collection counts under
# +:global_gc_count+ in every Ractor whose heap it collects, and under
# +:global_gc_invocation_count+ in the calling Ractor.
#
# Note that these keyword arguments are implementation- and version-dependent,
# are not guaranteed to be future-compatible,
# and may be ignored in some implementations.
def self.start full_mark: true, immediate_mark: true, immediate_sweep: true, global: true
Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false, global
end
# Alias of GC.start
def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true, global: true
Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false, global
end
# call-seq:
# GC.enable -> true or false
#
# Enables garbage collection;
# returns whether garbage collection was disabled:
#
# GC.disable
# GC.enable # => true
# GC.enable # => false
#
def self.enable
Primitive.gc_enable
end
# call-seq:
# GC.disable -> true or false
#
# Disables garbage collection (but GC.start remains potent):
# returns whether garbage collection was already disabled.
#
# GC.enable
# GC.disable # => false
# GC.disable # => true
#
def self.disable
Primitive.gc_disable
end
# call-seq:
# GC.stress -> setting
#
# Returns the current \GC stress-mode setting,
# which initially is +false+.
#
# The stress mode may be set by method GC.stress=.
def self.stress
Primitive.gc_stress_get
end
# call-seq:
# GC.stress = value -> value
#
# Enables or disables stress mode.
# Stress mode causes \GC to be invoked at every \GC opportunity
# (all memory and object allocations);
# it is intended only for debugging and severely degrades performance.
#
# Sets the current \GC stress mode to the given value:
#
# - If the value is +nil+ or +false+, disables stress mode.
# - If the value is an integer,
# enables stress mode with certain flags; see below.
# - Otherwise, enables stress mode without any flags.
#
# The flags are bits in the given integer:
#
# - +0x01+: no major \GC.
# - +0x02+: no immediate sweep.
# - +0x04+: full mark after malloc/calloc/realloc.
#
def self.stress=(flag)
Primitive.gc_stress_set_m flag
end
# call-seq:
# self.count -> integer
#
# Returns the total number of times garbage collection has occurred:
#
# With the default GC, counts the collections of the current Ractor's heap,
# including global collections that other Ractors started.
#
# GC.count # => 385
# GC.start
# GC.count # => 386
#
def self.count
Primitive.gc_count
end
# call-seq:
# GC.stat(process: false) -> new_hash
# GC.stat(key, process: false) -> value
# GC.stat(hash, process: false) -> hash
#
# This method is implementation-specific to CRuby.
#
# Returns \GC statistics.
# The particular statistics are implementation-specific
# and may change in the future without notice.
#
# With <tt>process: false</tt> (the default) or +nil+,
# returns statistics for the current Ractor's object space.
# With any other value of +process+, returns process-wide statistics;
# see "Process statistics" below.
#
# With no positional argument or +nil+ given,
# returns a hash containing the \GC statistics:
#
# GC.stat
# # =>
# {count: 1,
# time: 0,
# marking_time: 0,
# sweeping_time: 0,
# total_malloc_bytes: 10432791,
# total_free_bytes: 6989873,
# heap_allocated_pages: 41,
# heap_empty_pages: 0,
# heap_allocatable_bytes: 228656,
# heap_eden_pages: 41,
# total_allocated_pages: 41,
# total_freed_pages: 0,
# malloc_increase_bytes: 0,
# malloc_increase_bytes_limit: 16777216,
# minor_gc_count: 0,
# major_gc_count: 1,
# global_gc_count: 0,
# global_gc_invocation_count: 0,
# compact_count: 0,
# read_barrier_faults: 0,
# total_moved_objects: 0,
# remembered_wb_unprotected_objects: 0,
# remembered_wb_unprotected_objects_limit: 7,
# old_objects: 721,
# old_objects_limit: 1442,
# oldmalloc_increase_bytes: 0,
# oldmalloc_increase_bytes_limit: 16777216,
# total_allocated_objects: 36735,
# total_freed_objects: 19527,
# heap_available_slots: 40591,
# heap_live_slots: 17208,
# heap_free_slots: 23383,
# heap_final_slots: 0,
# heap_marked_slots: 17175}
#
# With symbol argument +key+ given,
# returns the value for that key:
#
# GC.stat(:count) # => 30
#
# With hash argument +hash+ given,
# returns that hash with GC statistics merged into its content;
# this form may be useful in minimizing {probe effects}[https://en.wikipedia.org/wiki/Probe_effect]:
#
# h = {foo: 0, bar: 1}
# GC.stat(h)
# h.keys.take(5) # => [:foo, :bar, :count, :time, :marking_time]
#
# The hash includes entries such as:
#
# - +:count+:
# The total number of garbage collections of this Ractor's heap since the
# object space was created. It includes minor, major, and global garbage
# collections, and equals +:minor_gc_count+ plus +:major_gc_count+ plus
# +:global_gc_count+.
# - +:time+:
# The total time spent in garbage collections (in milliseconds).
# - +:marking_time+:
# The total time spent marking objects (in milliseconds).
# - +:sweeping_time+:
# The total time spent sweeping objects (in milliseconds).
# - +:total_malloc_bytes+:
# The total number of bytes allocated by malloc since application start.
# - +:total_free_bytes+:
# The total number of bytes freed since application start.
# - +:heap_allocated_pages+:
# The total number of allocated pages.
# - +:heap_empty_pages+:
# The number of pages with no live objects, and that could be released to the system.
# - +:heap_allocatable_bytes+:
# The number of bytes that can be allocated without triggering a new
# garbage collection cycle.
# - +:heap_eden_pages+:
# The total number of pages which contain at least one live slot.
# - +:total_allocated_pages+:
# The cumulative number of pages allocated since application start.
# - +:total_freed_pages+:
# The cumulative number of pages freed since application start.
# - +:malloc_increase_bytes+:
# Amount of memory allocated on the heap for objects. Decreased by any \GC.
# - +:malloc_increase_bytes_limit+:
# When +:malloc_increase_bytes+ crosses this limit, \GC is triggered.
# - +:minor_gc_count+:
# The total number of local minor garbage collections of this Ractor's heap.
# - +:major_gc_count+:
# The total number of local major garbage collections of this Ractor's heap.
# Global garbage collections are counted separately.
# - +:global_gc_count+:
# The number of global (stop-the-world) garbage collections that collected
# this Ractor's heap. Any Ractor can start them.
# - +:global_gc_invocation_count+:
# The number of global garbage collections that this Ractor started.
# Each global garbage collection has exactly one starter.
# - +:compact_count+:
# The total number of compactions run since process start.
# - +:read_barrier_faults+:
# The total number of times the read barrier was triggered during compaction.
# - +:total_moved_objects+:
# The total number of objects compaction has moved.
# - +:remembered_wb_unprotected_objects+:
# The total number of objects without write barriers.
# - +:remembered_wb_unprotected_objects_limit+:
# When +:remembered_wb_unprotected_objects+ crosses this limit, major \GC is triggered.
# - +:old_objects+:
# Number of live, old objects which have survived at least 3 garbage collections.
# - +:old_objects_limit+:
# When +:old_objects+ crosses this limit, major \GC is triggered.
# - +:oldmalloc_increase_bytes+:
# Amount of memory allocated on the heap for objects. Decreased by major \GC.
# - +:oldmalloc_increase_bytes_limit+:
# When +:oldmalloc_increase_bytes+ crosses this limit, major \GC is triggered.
# - +:total_allocated_objects+:
# The cumulative number of objects allocated since application start.
# - +:total_freed_objects+:
# The cumulative number of objects freed since application start.
# - +:heap_available_slots+:
# The total number of slots in all +:heap_allocated_pages+.
# - +:heap_live_slots+:
# The total number of slots which contain live objects.
# - +:heap_free_slots+:
# The total number of slots which do not contain live objects.
# - +:heap_final_slots+:
# The total number of slots with pending finalizers to be run.
# - +:heap_marked_slots+:
# The number of objects in this Ractor's heap marked in the last \GC of
# that heap.
#
# === Process statistics
#
# With <tt>process: true</tt>, the default collector reports cumulative
# collection counts and measured CPU time across all Ractors, retaining
# history from destroyed object spaces, and the state of the page pool
# that all object spaces share:
#
# GC.stat(process: true)
# # => {count: 42, time: 3, marking_time: 2, sweeping_time: 1,
# # minor_gc_count: 32, major_gc_count: 8, global_gc_count: 2,
# # page_pool_arenas: 2, page_pool_arenas_freed: 0,
# # page_pool_total_pages: 64, page_pool_discarded_pages: 0}
#
# +:count+ is the number of garbage collections in the process. Each local
# and each global collection counts once, so +:count+ equals
# +:minor_gc_count+ plus +:major_gc_count+ plus +:global_gc_count+. It is not
# the sum of the Ractor +:count+ values, because one global collection
# collects every heap.
#
# +:global_gc_count+ is the number of global collections in the process,
# however many Ractors each one stops.
#
# The page pool keys are:
#
# - +:page_pool_arenas+:
# The number of page-pool arenas that are mapped now.
# - +:page_pool_arenas_freed+:
# The number of arenas that the page pool has unmapped.
# - +:page_pool_total_pages+:
# The number of page bodies in the mapped arenas.
# - +:page_pool_discarded_pages+:
# The number of free page bodies whose memory the page pool has returned
# to the operating system.
#
# +:time+ may exceed +:marking_time+ plus +:sweeping_time+ by one millisecond
# because raw nanoseconds are summed before conversion. Times measure collector
# CPU work, not wall-clock pauses.
#
# Reads combine object-space publications without requesting a stop-the-world
# barrier. GC.measure_total_time controls future timing accumulation. Profiler
# controls do not affect these totals. A forked child inherits its parent's
# totals. MMTk and wbcheck raise NotImplementedError for process statistics.
#
# Unavailable keys raise ArgumentError.
#
def self.stat hash_or_key = nil, process: false
Primitive.gc_stat hash_or_key, process
end
# call-seq:
# GC.stat_heap -> new_hash
# GC.stat_heap(heap_id) -> new_hash
# GC.stat_heap(heap_id, key) -> value
# GC.stat_heap(nil, hash) -> hash
# GC.stat_heap(heap_id, hash) -> hash
#
# This method is implementation-specific to CRuby.
#
# Returns statistics for \GC heaps.
# The particular statistics are implementation-specific
# and may change in the future without notice.
#
# With no argument given, returns statistics for all heaps:
#
# GC.stat_heap
# # =>
# {0 =>
# {slot_size: 32,
# heap_eden_pages: 24,
# heap_eden_slots: 12288,
# total_allocated_pages: 24,
# force_major_gc_count: 0,
# force_incremental_marking_finish_count: 0,
# total_allocated_objects: 8450,
# total_freed_objects: 3120},
# 1 =>
# {slot_size: 64,
# heap_eden_pages: 246,
# heap_eden_slots: 402802,
# total_allocated_pages: 246,
# force_major_gc_count: 2,
# force_incremental_marking_finish_count: 1,
# total_allocated_objects: 33867152,
# total_freed_objects: 33520523},
# 2 =>
# {slot_size: 128,
# heap_eden_pages: 84,
# heap_eden_slots: 68746,
# total_allocated_pages: 84,
# force_major_gc_count: 1,
# force_incremental_marking_finish_count: 4,
# total_allocated_objects: 147491,
# total_freed_objects: 90699},
# 3 =>
# {slot_size: 256,
# heap_eden_pages: 157,
# heap_eden_slots: 64182,
# total_allocated_pages: 157,
# force_major_gc_count: 0,
# force_incremental_marking_finish_count: 0,
# total_allocated_objects: 211460,
# total_freed_objects: 190075},
# 4 =>
# {slot_size: 512,
# heap_eden_pages: 8,
# heap_eden_slots: 1631,
# total_allocated_pages: 8,
# force_major_gc_count: 0,
# force_incremental_marking_finish_count: 0,
# total_allocated_objects: 1422,
# total_freed_objects: 700},
# 5 =>
# {slot_size: 1024,
# heap_eden_pages: 16,
# heap_eden_slots: 1628,
# total_allocated_pages: 16,
# force_major_gc_count: 0,
# force_incremental_marking_finish_count: 0,
# total_allocated_objects: 1230,
# total_freed_objects: 309}}
#
# In the example above, the keys in the outer hash are the heap identifiers:
#
# GC.stat_heap.keys # => [0, 1, 2, 3, 4, 5]
#
# On CRuby, each heap identifier is an integer;
# on other implementations, a heap identifier may be a string.
#
# With only argument +heap_id+ given,
# returns statistics for the given heap identifier:
#
# GC.stat_heap(3)
# # =>
# {slot_size: 256,
# heap_eden_pages: 157,
# heap_eden_slots: 64182,
# total_allocated_pages: 157,
# force_major_gc_count: 0,
# force_incremental_marking_finish_count: 0,
# total_allocated_objects: 225018,
# total_freed_objects: 206647}
#
# With arguments +heap_id+ and +key+ given,
# returns the value for the given key in the given heap:
#
# GC.stat_heap(3, :slot_size) # => 256
#
# With arguments +nil+ and +hash+ given,
# merges the statistics for all heaps into the given hash:
#
# h = {foo: 0, bar: 1}
# GC.stat_heap(nil, h).keys # => [:foo, :bar, 0, 1, 2, 3, 4]
#
# With arguments +heap_id+ and +hash+ given,
# merges the statistics for the given heap into the given hash:
#
# h = {foo: 0, bar: 1}
# GC.stat_heap(2, h).keys
# # =>
# [:foo,
# :bar,
# :slot_size,
# :heap_eden_pages,
# :heap_eden_slots,
# :total_allocated_pages,
# :force_major_gc_count,
# :force_incremental_marking_finish_count,
# :total_allocated_objects,
# :total_freed_objects]
#
# The statistics for a heap may include:
#
# - +:slot_size+:
# The slot size of the heap in bytes.
# - +:heap_allocatable_pages+:
# The number of pages that can be allocated without triggering a new
# garbage collection cycle.
# - +:heap_eden_pages+:
# The number of pages in the eden heap.
# - +:heap_eden_slots+:
# The total number of slots in all of the pages in the eden heap.
# - +:total_allocated_pages+:
# The total number of pages that have been allocated in the heap.
# - +:total_freed_pages+:
# The total number of pages that have been freed and released back to the
# system in the heap.
# - +:force_major_gc_count+:
# The number of times this heap has forced major garbage collection cycles
# to start due to running out of free slots.
# - +:force_incremental_marking_finish_count+:
# The number of times this heap has forced incremental marking to complete
# due to running out of pooled slots.
#
def self.stat_heap heap_name = nil, hash_or_key = nil
Primitive.gc_stat_heap heap_name, hash_or_key
end
# call-seq:
# GC.config -> hash
# GC.config(hash_to_merge) -> hash
#
# This method is implementation-specific to CRuby.
#
# Sets or gets information about the current \GC configuration.
#
# Configuration parameters are \GC implementation-specific and may change without notice.
#
# With no argument given, returns a hash containing the configuration:
#
# GC.config
# # => {rgengc_allow_full_mark: true, implementation: "default"}
#
# With argument +hash_to_merge+ given,
# merges that hash into the stored configuration hash;
# ignores unknown hash keys;
# returns the configuration hash:
#
# GC.config(rgengc_allow_full_mark: false)
# # => {rgengc_allow_full_mark: false, implementation: "default"}
# GC.config(foo: 'bar')
# # => {rgengc_allow_full_mark: false, implementation: "default"}
#
# <b>All-Implementations Configuration</b>
#
# The single read-only entry for all implementations is:
#
# - +:implementation+:
# the string name of the implementation;
# for the Ruby default implementation, <tt>'default'</tt>.
#
# <b>Implementation-Specific Configuration</b>
#
# A \GC implementation maintains its own implementation-specific configuration.
#
# For Ruby's default implementation the single entry is:
#
# - +:rgengc_allow_full_mark+:
# Controls whether the \GC is allowed to run a full mark (young & old objects):
#
# - +true+ (default): \GC interleaves major and minor collections.
# A flag is set to notify GC that a full mark has been requested.
# This flag is accessible via GC.latest_gc_info(:need_major_by).
# - +false+: \GC does not initiate a full marking cycle unless explicitly directed by user code;
# see GC.start.
# Setting this parameter to +false+ disables young-to-old promotion.
# For performance reasons, we recommended warming up the application using Process.warmup
# before setting this parameter to +false+.
#
def self.config hash = nil
if Primitive.cexpr!("RBOOL(RB_TYPE_P(hash, T_HASH))")
if hash.include?(:implementation)
raise ArgumentError, 'Attempting to set read-only key "Implementation"'
end
Primitive.gc_config_set hash
elsif hash != nil
raise ArgumentError
end
Primitive.gc_config_get
end
# call-seq:
# GC.latest_gc_info -> new_hash
# GC.latest_gc_info(key) -> value
# GC.latest_gc_info(hash) -> hash
#
# With no argument given,
# returns information about the most recent garbage collection:
#
# GC.latest_gc_info
# # =>
# {major_by: :force,
# need_major_by: nil,
# gc_by: :method,
# have_finalizer: false,
# immediate_sweep: true,
# state: :none,
# weak_references_count: 0,
# retained_weak_references_count: 0}
#
# With symbol argument +key+ given,
# returns the value for that key:
#
# GC.latest_gc_info(:gc_by) # => :newobj
#
# With hash argument +hash+ given,
# returns that hash with GC information merged into its content;
# this form may be useful in minimizing {probe effects}[https://en.wikipedia.org/wiki/Probe_effect]:
#
# h = {foo: 0, bar: 1}
# GC.latest_gc_info(h)
# # =>
# {foo: 0,
# bar: 1,
# major_by: nil,
# need_major_by: nil,
# gc_by: :newobj,
# have_finalizer: false,
# immediate_sweep: false,
# state: :sweeping,
# weak_references_count: 0,
# retained_weak_references_count: 0}
#
def self.latest_gc_info hash_or_key = nil
if hash_or_key == nil
hash_or_key = {}
elsif Primitive.cexpr!("RBOOL(!SYMBOL_P(hash_or_key) && !RB_TYPE_P(hash_or_key, T_HASH))")
raise TypeError, "non-hash or symbol given"
end
Primitive.cstmt! %{
return rb_gc_latest_gc_info(hash_or_key);
}
end
# call-seq:
# GC.measure_total_time = setting -> setting
#
# Enables or disables \GC total time measurement;
# returns +setting+.
# See GC.total_time.
#
# When argument +object+ is +nil+ or +false+, disables total time measurement;
# GC.measure_total_time then returns +false+:
#
# GC.measure_total_time = nil # => nil
# GC.measure_total_time # => false
# GC.measure_total_time = false # => false
# GC.measure_total_time # => false
#
# Otherwise, enables total time measurement;
# GC.measure_total_time then returns +true+:
#
# GC.measure_total_time = true # => true
# GC.measure_total_time # => true
# GC.measure_total_time = :foo # => :foo
# GC.measure_total_time # => true
#
# Note that when enabled, total time measurement affects performance.
#
# The setting is per-Ractor: it affects only the Ractor that sets it.
# With the default GC, a Ractor created by Ractor.new inherits the
# setting of the Ractor that creates it; the main Ractor starts with
# measurement enabled.
def self.measure_total_time=(flag)
Primitive.cstmt! %{
rb_gc_impl_set_measure_total_time(rb_gc_get_objspace(), flag);
return flag;
}
end
# call-seq:
# GC.measure_total_time -> true or false
#
# Returns the setting for \GC total time measurement in the current
# Ractor; the main Ractor's initial setting is +true+ and, with the
# default GC, other Ractors inherit their creator's setting.
# See GC.total_time.
def self.measure_total_time
Primitive.cexpr! %{
RBOOL(rb_gc_impl_get_measure_total_time(rb_gc_get_objspace()))
}
end
# call-seq:
# GC.total_time -> integer
#
# Returns the \GC total time in nanoseconds:
#
# With the default collector, this is the current Ractor's measured GC CPU
# work, not wall-clock pause time.
#
# GC.total_time # => 156250
#
# Note that total time accumulates
# only when total time measurement is enabled
# (that is, when GC.measure_total_time is +true+):
#
# GC.measure_total_time # => true
# GC.total_time # => 625000
# GC.start
# GC.total_time # => 937500
# GC.start
# GC.total_time # => 1093750
#
# GC.measure_total_time = false
# GC.total_time # => 1250000
# GC.start
# GC.total_time # => 1250000
# GC.start
# GC.total_time # => 1250000
#
# GC.measure_total_time = true
# GC.total_time # => 1250000
# GC.start
# GC.total_time # => 1406250
#
def self.total_time
Primitive.cexpr! %{
ULL2NUM(rb_gc_impl_get_total_time(rb_gc_get_objspace()))
}
end
end
module ObjectSpace
# Alias of GC.start
def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true, global: true
Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false, global
end
module_function :garbage_collect
end