* Copyright 2011 Xamarin, Inc.
* Copyright (C) 2012 Xamarin Inc
*
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License 2.0 as published by the Free Software Foundation;
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License 2.0 along with this library; if not, write to the Free
- * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
*
* Important: allocation provides always zeroed memory, having to do
* a memset after allocation is deadly for performance.
static gboolean do_dump_nursery_content = FALSE;
static gboolean enable_nursery_canaries = FALSE;
+static gboolean precleaning_enabled = TRUE;
+
#ifdef HEAVY_STATISTICS
guint64 stat_objects_alloced_degraded = 0;
guint64 stat_bytes_alloced_degraded = 0;
#define safe_object_get_size sgen_safe_object_get_size
+#if defined(PLATFORM_MACOSX) || defined(HOST_WIN32) || (defined(__linux__) && !defined(PLATFORM_ANDROID))
+/* Use concurrent major on deskstop platforms */
+#define DEFAULT_MAJOR_INIT sgen_marksweep_conc_init
+#define DEFAULT_MAJOR_NAME "marksweep-conc"
+#else
+#define DEFAULT_MAJOR_INIT sgen_marksweep_init
+#define DEFAULT_MAJOR_NAME "marksweep"
+#endif
+
/*
* ######################################################################
* ######## Global data.
* ######################################################################
*/
MonoCoopMutex gc_mutex;
-gboolean sgen_try_free_some_memory;
#define SCAN_START_SIZE SGEN_SCAN_START_SIZE
pin_object (obj_to_pin);
GRAY_OBJECT_ENQUEUE (queue, obj_to_pin, desc);
- sgen_pin_stats_register_object (obj_to_pin, obj_to_pin_size);
+ sgen_pin_stats_register_object (obj_to_pin, GENERATION_NURSERY);
definitely_pinned [count] = obj_to_pin;
count++;
}
+ if (concurrent_collection_in_progress)
+ sgen_pinning_register_pinned_in_nursery (obj_to_pin);
next_pin_queue_entry:
last = addr;
void
sgen_pin_object (GCObject *object, GrayQueue *queue)
{
+ SGEN_ASSERT (0, sgen_ptr_in_nursery (object), "We're only supposed to use this for pinning nursery objects when out of memory.");
+
/*
* All pinned objects are assumed to have been staged, so we need to stage as well.
* Also, the count of staged objects shows that "late pinning" happened.
binary_protocol_pin (object, (gpointer)LOAD_VTABLE (object), safe_object_get_size (object));
++objects_pinned;
- sgen_pin_stats_register_object (object, safe_object_get_size (object));
+ sgen_pin_stats_register_object (object, GENERATION_NURSERY);
GRAY_OBJECT_ENQUEUE (queue, object, sgen_obj_get_descriptor_safe (object));
}
if (sgen_client_bridge_need_processing ())
sgen_client_bridge_reset_data ();
+ /*
+ * Mark all strong toggleref objects. This must be done before we walk ephemerons or finalizers
+ * to ensure they see the full set of live objects.
+ */
+ sgen_client_mark_togglerefs (start_addr, end_addr, ctx);
+
/*
* Walk the ephemeron tables marking all values with reachable keys. This must be completely done
* before processing finalizable objects and non-tracking weak links to avoid finalizing/clearing
++ephemeron_rounds;
} while (!done_with_ephemerons);
- sgen_client_mark_togglerefs (start_addr, end_addr, ctx);
-
if (sgen_client_bridge_need_processing ()) {
/*Make sure the gray stack is empty before we process bridge objects so we get liveness right*/
sgen_drain_gray_stack (ctx);
sgen_client_clear_togglerefs (start_addr, end_addr, ctx);
TV_GETTIME (btv);
- SGEN_LOG (2, "Finalize queue handling scan for %s generation: %ld usecs %d ephemeron rounds", generation_name (generation), TV_ELAPSED (atv, btv), ephemeron_rounds);
+ SGEN_LOG (2, "Finalize queue handling scan for %s generation: %lld usecs %d ephemeron rounds", generation_name (generation), (long long)TV_ELAPSED (atv, btv), ephemeron_rounds);
/*
* handle disappearing links
major_collector.scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx);
sgen_los_scan_card_table (CARDTABLE_SCAN_MOD_UNION_PRECLEAN, ctx);
+
+ sgen_scan_pin_queue_objects (ctx);
}
static void
* Return whether any objects were late-pinned due to being out of memory.
*/
static gboolean
-collect_nursery (SgenGrayQueue *unpin_queue, gboolean finish_up_concurrent_mark)
+collect_nursery (const char *reason, gboolean is_overflow, SgenGrayQueue *unpin_queue, gboolean finish_up_concurrent_mark)
{
gboolean needs_major;
size_t max_garbage_amount;
TV_GETTIME (atv);
time_minor_pinning += TV_ELAPSED (btv, atv);
- SGEN_LOG (2, "Finding pinned pointers: %zd in %ld usecs", sgen_get_pinned_count (), TV_ELAPSED (btv, atv));
+ SGEN_LOG (2, "Finding pinned pointers: %zd in %lld usecs", sgen_get_pinned_count (), (long long)TV_ELAPSED (btv, atv));
SGEN_LOG (4, "Start scan with %zd pinned objects", sgen_get_pinned_count ());
sj = (ScanJob*)sgen_thread_pool_job_alloc ("scan remset", job_remembered_set_scan, sizeof (ScanJob));
/* we don't have complete write barrier yet, so we scan all the old generation sections */
TV_GETTIME (btv);
time_minor_scan_remsets += TV_ELAPSED (atv, btv);
- SGEN_LOG (2, "Old generation scan: %ld usecs", TV_ELAPSED (atv, btv));
+ SGEN_LOG (2, "Old generation scan: %lld usecs", (long long)TV_ELAPSED (atv, btv));
- sgen_pin_stats_print_class_stats ();
+ sgen_pin_stats_report ();
/* FIXME: Why do we do this at this specific, seemingly random, point? */
sgen_client_collecting_minor (&fin_ready_queue, &critical_fin_queue);
sgen_client_binary_protocol_reclaim_end (GENERATION_NURSERY);
TV_GETTIME (btv);
time_minor_fragment_creation += TV_ELAPSED (atv, btv);
- SGEN_LOG (2, "Fragment creation: %ld usecs, %lu bytes available", TV_ELAPSED (atv, btv), (unsigned long)fragment_total);
+ SGEN_LOG (2, "Fragment creation: %lld usecs, %lu bytes available", (long long)TV_ELAPSED (atv, btv), (unsigned long)fragment_total);
if (consistency_check_at_minor_collection)
sgen_check_major_refs ();
binary_protocol_flush_buffers (FALSE);
- sgen_memgov_minor_collection_end ();
+ sgen_memgov_minor_collection_end (reason, is_overflow);
/*objects are late pinned because of lack of memory, so a major is a good call*/
needs_major = objects_pinned > 0;
sgen_client_pre_collection_checks ();
- if (!concurrent) {
+ if (mode != COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
/* Remsets are not useful for a major collection */
remset.clear_cards ();
}
sgen_init_pinning ();
SGEN_LOG (6, "Collecting pinned addresses");
pin_from_roots ((void*)lowest_heap_address, (void*)highest_heap_address, ctx);
-
+ if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
+ /* Pin cemented objects that were forced */
+ sgen_pin_cemented_objects ();
+ }
sgen_optimize_pin_queue ();
+ if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
+ /*
+ * Cemented objects that are in the pinned list will be marked. When
+ * marking concurrently we won't mark mod-union cards for these objects.
+ * Instead they will remain cemented until the next major collection,
+ * when we will recheck if they are still pinned in the roots.
+ */
+ sgen_cement_force_pinned ();
+ }
sgen_client_collecting_major_1 ();
sgen_los_pin_object (bigobj->data);
if (SGEN_OBJECT_HAS_REFERENCES (bigobj->data))
GRAY_OBJECT_ENQUEUE (WORKERS_DISTRIBUTE_GRAY_QUEUE, bigobj->data, sgen_obj_get_descriptor ((GCObject*)bigobj->data));
- sgen_pin_stats_register_object (bigobj->data, safe_object_get_size (bigobj->data));
+ sgen_pin_stats_register_object (bigobj->data, GENERATION_OLD);
SGEN_LOG (6, "Marked large object %p (%s) size: %lu from roots", bigobj->data,
sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (bigobj->data)),
(unsigned long)sgen_los_object_size (bigobj));
TV_GETTIME (btv);
time_major_pinning += TV_ELAPSED (atv, btv);
- SGEN_LOG (2, "Finding pinned pointers: %zd in %ld usecs", sgen_get_pinned_count (), TV_ELAPSED (atv, btv));
+ SGEN_LOG (2, "Finding pinned pointers: %zd in %lld usecs", sgen_get_pinned_count (), (long long)TV_ELAPSED (atv, btv));
SGEN_LOG (4, "Start scan with %zd pinned objects", sgen_get_pinned_count ());
major_collector.init_to_space ();
SGEN_ASSERT (0, sgen_workers_all_done (), "Why are the workers not done when we start or finish a major collection?");
- /*
- * The concurrent collector doesn't move objects, neither on
- * the major heap nor in the nursery, so we can mark even
- * before pinning has finished. For the non-concurrent
- * collector we start the workers after pinning.
- */
- if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
- ScanJob *sj;
- /* Mod union preclean job */
- sj = (ScanJob*)sgen_thread_pool_job_alloc ("preclean mod union cardtable", job_mod_union_preclean, sizeof (ScanJob));
- sj->ops = object_ops;
- sgen_workers_start_all_workers (object_ops, &sj->job);
- gray_queue_enable_redirect (WORKERS_DISTRIBUTE_GRAY_QUEUE);
- } else if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
+ if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
if (sgen_workers_have_idle_work ()) {
+ /*
+ * We force the finish of the worker with the new object ops context
+ * which can also do copying. We need to have finished pinning.
+ */
sgen_workers_start_all_workers (object_ops, NULL);
sgen_workers_join ();
}
sgen_client_collecting_major_3 (&fin_ready_queue, &critical_fin_queue);
- /*
- * FIXME: is this the right context? It doesn't seem to contain a copy function
- * unless we're concurrent.
- */
- enqueue_scan_from_roots_jobs (heap_start, heap_end, object_ops, mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT);
+ enqueue_scan_from_roots_jobs (heap_start, heap_end, object_ops, FALSE);
TV_GETTIME (btv);
time_major_scan_roots += TV_ELAPSED (atv, btv);
+ /*
+ * We start the concurrent worker after pinning and after we scanned the roots
+ * in order to make sure that the worker does not finish before handling all
+ * the roots.
+ */
+ if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
+ if (precleaning_enabled) {
+ ScanJob *sj;
+ /* Mod union preclean job */
+ sj = (ScanJob*)sgen_thread_pool_job_alloc ("preclean mod union cardtable", job_mod_union_preclean, sizeof (ScanJob));
+ sj->ops = object_ops;
+ sgen_workers_start_all_workers (object_ops, &sj->job);
+ } else {
+ sgen_workers_start_all_workers (object_ops, NULL);
+ }
+ gray_queue_enable_redirect (WORKERS_DISTRIBUTE_GRAY_QUEUE);
+ }
+
if (mode == COPY_OR_MARK_FROM_ROOTS_FINISH_CONCURRENT) {
ScanJob *sj;
time_major_scan_mod_union += TV_ELAPSED (btv, atv);
}
- sgen_pin_stats_print_class_stats ();
+ sgen_pin_stats_report ();
}
static void
major_finish_copy_or_mark (CopyOrMarkFromRootsMode mode)
{
if (mode == COPY_OR_MARK_FROM_ROOTS_START_CONCURRENT) {
- /*
- * Prepare the pin queue for the next collection. Since pinning runs on the worker
- * threads we must wait for the jobs to finish before we can reset it.
- */
- sgen_workers_wait_for_jobs_finished ();
sgen_finish_pinning ();
sgen_pin_stats_reset ();
}
static void
-major_start_collection (gboolean concurrent, size_t *old_next_pin_slot)
+major_start_collection (const char *reason, gboolean concurrent, size_t *old_next_pin_slot)
{
SgenObjectOperations *object_ops;
reset_pinned_from_failed_allocation ();
- sgen_memgov_major_collection_start ();
+ sgen_memgov_major_collection_start (concurrent, reason);
//count_ref_nonref_objs ();
//consistency_check ();
}
static void
-major_finish_collection (const char *reason, size_t old_next_pin_slot, gboolean forced)
+major_finish_collection (const char *reason, gboolean is_overflow, size_t old_next_pin_slot, gboolean forced)
{
ScannedObjectCounts counts;
SgenObjectOperations *object_ops;
g_assert (sgen_gray_object_queue_is_empty (&gray_queue));
- sgen_memgov_major_collection_end (forced);
+ sgen_memgov_major_collection_end (forced, concurrent_collection_in_progress, reason, is_overflow);
current_collection_generation = -1;
memset (&counts, 0, sizeof (ScannedObjectCounts));
}
static gboolean
-major_do_collection (const char *reason, gboolean forced)
+major_do_collection (const char *reason, gboolean is_overflow, gboolean forced)
{
TV_DECLARE (time_start);
TV_DECLARE (time_end);
/* world must be stopped already */
TV_GETTIME (time_start);
- major_start_collection (FALSE, &old_next_pin_slot);
- major_finish_collection (reason, old_next_pin_slot, forced);
+ major_start_collection (reason, FALSE, &old_next_pin_slot);
+ major_finish_collection (reason, is_overflow, old_next_pin_slot, forced);
TV_GETTIME (time_end);
gc_stats.major_gc_time += TV_ELAPSED (time_start, time_end);
binary_protocol_concurrent_start ();
// FIXME: store reason and pass it when finishing
- major_start_collection (TRUE, NULL);
+ major_start_collection (reason, TRUE, NULL);
gray_queue_redirect (&gray_queue);
current_collection_generation = GENERATION_OLD;
sgen_cement_reset ();
- major_finish_collection ("finishing", -1, forced);
+ major_finish_collection ("finishing", FALSE, -1, forced);
if (whole_heap_check_before_collection)
sgen_check_whole_heap (FALSE);
void
sgen_perform_collection (size_t requested_size, int generation_to_collect, const char *reason, gboolean wait_to_finish)
{
- TV_DECLARE (gc_start);
- TV_DECLARE (gc_end);
TV_DECLARE (gc_total_start);
TV_DECLARE (gc_total_end);
- GGTimingInfo infos [2];
int overflow_generation_to_collect = -1;
int oldest_generation_collected = generation_to_collect;
const char *overflow_reason = NULL;
+ gboolean finish_concurrent = concurrent_collection_in_progress && (major_should_finish_concurrent_collection () || generation_to_collect == GENERATION_OLD);
binary_protocol_collection_requested (generation_to_collect, requested_size, wait_to_finish ? 1 : 0);
SGEN_ASSERT (0, generation_to_collect == GENERATION_NURSERY || generation_to_collect == GENERATION_OLD, "What generation is this?");
- TV_GETTIME (gc_start);
-
sgen_stop_world (generation_to_collect);
TV_GETTIME (gc_total_start);
- if (concurrent_collection_in_progress) {
- /*
- * If the concurrent worker is finished or we are asked to do a major collection
- * then we finish the concurrent collection.
- */
- gboolean finish = major_should_finish_concurrent_collection () || generation_to_collect == GENERATION_OLD;
-
- if (finish) {
- major_finish_concurrent_collection (wait_to_finish);
- oldest_generation_collected = GENERATION_OLD;
- } else {
- SGEN_ASSERT (0, generation_to_collect == GENERATION_NURSERY, "Why aren't we finishing the concurrent collection?");
+ // FIXME: extract overflow reason
+ // FIXME: minor overflow for concurrent case
+ if (generation_to_collect == GENERATION_NURSERY && !finish_concurrent) {
+ if (concurrent_collection_in_progress)
major_update_concurrent_collection ();
- collect_nursery (NULL, FALSE);
- }
-
- goto done;
- }
-
- SGEN_ASSERT (0, !concurrent_collection_in_progress, "Why did this not get handled above?");
- /*
- * There's no concurrent collection in progress. Collect the generation we're asked
- * to collect. If the major collector is concurrent and we're not forced to wait,
- * start a concurrent collection.
- */
- // FIXME: extract overflow reason
- if (generation_to_collect == GENERATION_NURSERY) {
- if (collect_nursery (NULL, FALSE)) {
+ if (collect_nursery (reason, FALSE, NULL, FALSE) && !concurrent_collection_in_progress) {
overflow_generation_to_collect = GENERATION_OLD;
overflow_reason = "Minor overflow";
}
+ } else if (finish_concurrent) {
+ major_finish_concurrent_collection (wait_to_finish);
+ oldest_generation_collected = GENERATION_OLD;
} else {
+ SGEN_ASSERT (0, generation_to_collect == GENERATION_OLD, "We should have handled nursery collections above");
if (major_collector.is_concurrent && !wait_to_finish) {
- collect_nursery (NULL, FALSE);
+ collect_nursery ("Concurrent start", FALSE, NULL, FALSE);
major_start_concurrent_collection (reason);
- // FIXME: set infos[0] properly
- goto done;
- }
-
- if (major_do_collection (reason, wait_to_finish)) {
+ oldest_generation_collected = GENERATION_NURSERY;
+ } else if (major_do_collection (reason, FALSE, wait_to_finish)) {
overflow_generation_to_collect = GENERATION_NURSERY;
overflow_reason = "Excessive pinning";
}
}
- TV_GETTIME (gc_end);
-
- memset (infos, 0, sizeof (infos));
- infos [0].generation = generation_to_collect;
- infos [0].reason = reason;
- infos [0].is_overflow = FALSE;
- infos [1].generation = -1;
- infos [0].total_time = SGEN_TV_ELAPSED (gc_start, gc_end);
-
- SGEN_ASSERT (0, !concurrent_collection_in_progress, "Why did this not get handled above?");
-
if (overflow_generation_to_collect != -1) {
+ SGEN_ASSERT (0, !concurrent_collection_in_progress, "We don't yet support overflow collections with the concurrent collector");
+
/*
* We need to do an overflow collection, either because we ran out of memory
* or the nursery is fully pinned.
*/
- infos [1].generation = overflow_generation_to_collect;
- infos [1].reason = overflow_reason;
- infos [1].is_overflow = TRUE;
- gc_start = gc_end;
-
if (overflow_generation_to_collect == GENERATION_NURSERY)
- collect_nursery (NULL, FALSE);
+ collect_nursery (overflow_reason, TRUE, NULL, FALSE);
else
- major_do_collection (overflow_reason, wait_to_finish);
-
- TV_GETTIME (gc_end);
- infos [1].total_time = SGEN_TV_ELAPSED (gc_start, gc_end);
+ major_do_collection (overflow_reason, TRUE, wait_to_finish);
oldest_generation_collected = MAX (oldest_generation_collected, overflow_generation_to_collect);
}
degraded_mode = 1;
}
- done:
g_assert (sgen_gray_object_queue_is_empty (&gray_queue));
TV_GETTIME (gc_total_end);
time_max = MAX (time_max, TV_ELAPSED (gc_total_start, gc_total_end));
- sgen_restart_world (oldest_generation_collected, infos);
+ sgen_restart_world (oldest_generation_collected);
}
/*
}
}
- if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep")) {
- use_marksweep_major:
+ if (!major_collector_opt) {
+ use_default_major:
+ DEFAULT_MAJOR_INIT (&major_collector);
+ } else if (!strcmp (major_collector_opt, "marksweep")) {
sgen_marksweep_init (&major_collector);
- } else if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep-conc")) {
+ } else if (!strcmp (major_collector_opt, "marksweep-conc")) {
sgen_marksweep_conc_init (&major_collector);
} else {
- sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using `marksweep` instead.", "Unknown major collector `%s'.", major_collector_opt);
- goto use_marksweep_major;
+ sgen_env_var_error (MONO_GC_PARAMS_NAME, "Using `" DEFAULT_MAJOR_NAME "` instead.", "Unknown major collector `%s'.", major_collector_opt);
+ goto use_default_major;
}
sgen_nursery_size = DEFAULT_NURSERY_SIZE;
continue;
}
+ if (!strcmp (opt, "precleaning")) {
+ precleaning_enabled = TRUE;
+ continue;
+ }
+ if (!strcmp (opt, "no-precleaning")) {
+ precleaning_enabled = FALSE;
+ continue;
+ }
+
if (major_collector.handle_gc_param && major_collector.handle_gc_param (opt))
continue;
alloc_nursery ();
+ sgen_pinning_init ();
sgen_cement_init (cement_enabled);
if ((env = g_getenv (MONO_GC_DEBUG_NAME))) {
void
sgen_gc_unlock (void)
{
- gboolean try_free = sgen_try_free_some_memory;
- sgen_try_free_some_memory = FALSE;
mono_coop_mutex_unlock (&gc_mutex);
- if (try_free)
- mono_thread_hazardous_try_free_some ();
}
void
major_collector.iterate_live_block_ranges (callback);
}
+void
+sgen_major_collector_iterate_block_ranges (sgen_cardtable_block_callback callback)
+{
+ major_collector.iterate_block_ranges (callback);
+}
+
SgenMajorCollector*
sgen_get_major_collector (void)
{
/* LOCKING: assumes the GC lock is held */
void
-sgen_restart_world (int generation, GGTimingInfo *timing)
+sgen_restart_world (int generation)
{
long long major_total = -1, major_marked = -1, los_total = -1, los_marked = -1;
+ gint64 stw_time;
SGEN_ASSERT (0, world_is_stopped, "Why are we restarting a running world?");
count_cards (&major_total, &major_marked, &los_total, &los_marked);
binary_protocol_world_restarting (generation, sgen_timestamp (), major_total, major_marked, los_total, los_marked);
- sgen_client_restart_world (generation, timing);
-
world_is_stopped = FALSE;
- binary_protocol_world_restarted (generation, sgen_timestamp ());
+ sgen_client_restart_world (generation, &stw_time);
- sgen_try_free_some_memory = TRUE;
+ binary_protocol_world_restarted (generation, sgen_timestamp ());
if (sgen_client_bridge_need_processing ())
sgen_client_bridge_processing_finish (generation);
- sgen_memgov_collection_end (generation, timing, timing ? 2 : 0);
+ sgen_memgov_collection_end (generation, stw_time);
}
gboolean
sgen_stop_world (0);
sgen_clear_nursery_fragments ();
sgen_check_whole_heap (FALSE);
- sgen_restart_world (0, NULL);
+ sgen_restart_world (0);
}
gint64