delete for good
[mono.git] / mono / metadata / sgen-gc.c
index 3607182b8366f0637c4eaf21e098707866d401c2..ef173d4701eb183c9d0d98aaf2b836b7f298ee15 100644 (file)
 #include "metadata/mono-gc.h"
 #include "metadata/method-builder.h"
 #include "metadata/profiler-private.h"
+#include "metadata/monitor.h"
 #include "utils/mono-mmap.h"
 
 #ifdef HAVE_VALGRIND_MEMCHECK_H
@@ -530,12 +531,32 @@ struct _FinalizeEntry {
        void *object;
 };
 
+typedef struct _FinalizeEntryHashTable FinalizeEntryHashTable;
+struct _FinalizeEntryHashTable {
+       FinalizeEntry **table;
+       mword size;
+       int num_registered;
+};
+
 typedef struct _DisappearingLink DisappearingLink;
 struct _DisappearingLink {
        DisappearingLink *next;
        void **link;
 };
 
+typedef struct _DisappearingLinkHashTable DisappearingLinkHashTable;
+struct _DisappearingLinkHashTable {
+       DisappearingLink **table;
+       mword size;
+       int num_links;
+};
+
+enum {
+       GENERATION_NURSERY,
+       GENERATION_OLD,
+       GENERATION_MAX
+};
+
 /*
  * The link pointer is hidden by negating each bit.  We use the lowest
  * bit of the link (before negation) to store whether it needs
@@ -551,17 +572,16 @@ struct _DisappearingLink {
  * The finalizable hash has the object as the key, the 
  * disappearing_link hash, has the link address as key.
  */
-static FinalizeEntry **finalizable_hash = NULL;
+static FinalizeEntryHashTable minor_finalizable_hash;
+static FinalizeEntryHashTable major_finalizable_hash;
 /* objects that are ready to be finalized */
 static FinalizeEntry *fin_ready_list = NULL;
 static FinalizeEntry *critical_fin_list = NULL;
-static DisappearingLink **disappearing_link_hash = NULL;
-static mword disappearing_link_hash_size = 0;
-static mword finalizable_hash_size = 0;
 
-static int num_registered_finalizers = 0;
+static DisappearingLinkHashTable minor_disappearing_link_hash;
+static DisappearingLinkHashTable major_disappearing_link_hash;
+
 static int num_ready_finalizers = 0;
-static int num_disappearing_links = 0;
 static int no_finalize = 0;
 
 /* keep each size a multiple of ALLOC_ALIGN */
@@ -590,6 +610,19 @@ obj_is_from_pinned_alloc (char *p)
        return FALSE;
 }
 
+static int slot_for_size (size_t size);
+
+static void
+free_pinned_object (PinnedChunk *chunk, char *obj, size_t size)
+{
+       void **p = (void**)obj;
+       int slot = slot_for_size (size);
+
+       g_assert (obj >= (char*)chunk->start_data && obj < ((char*)chunk + chunk->num_pages * FREELIST_PAGESIZE));
+       *p = chunk->free_list [slot];
+       chunk->free_list [slot] = p;
+}
+
 enum {
        ROOT_TYPE_NORMAL = 0, /* "normal" roots */
        ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */
@@ -713,18 +746,24 @@ static void scan_from_remsets (void *start_nursery, void *end_nursery);
 static void find_pinning_ref_from_thread (char *obj, size_t size);
 static void update_current_thread_stack (void *start);
 static GCMemSection* alloc_section (size_t size);
-static void finalize_in_range (char *start, char *end);
-static void null_link_in_range (char *start, char *end);
+static void finalize_in_range (char *start, char *end, int generation);
+static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track,
+       DisappearingLinkHashTable *hash);
+static void null_link_in_range (char *start, char *end, int generation);
+static void null_links_for_domain (MonoDomain *domain, int generation);
 static gboolean search_fragment_for_size (size_t size);
 static void mark_pinned_from_addresses (PinnedChunk *chunk, void **start, void **end);
 static void clear_remsets (void);
 static void clear_tlabs (void);
 static char *find_tlab_next_from_address (char *addr);
+static void scan_pinned_objects (void (*callback) (PinnedChunk*, char*, size_t, void*), void *callback_data);
 static void sweep_pinned_objects (void);
 static void scan_from_pinned_objects (char *addr_start, char *addr_end);
 static void free_large_object (LOSObject *obj);
 static void free_mem_section (GCMemSection *section);
 
+static void mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track);
+
 void describe_ptr (char *ptr);
 void check_consistency (void);
 char* check_object (char *start);
@@ -1176,7 +1215,6 @@ static mword obj_references_checked = 0;
  * This section of code deals with detecting the objects no longer in use
  * and reclaiming the memory.
  */
-#if 0
 static void __attribute__((noinline))
 scan_area (char *start, char *end)
 {
@@ -1213,9 +1251,7 @@ scan_area (char *start, char *end)
                        type_rlen++;
                        continue;
                } else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
-                       skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
-                       skip_size *= mono_array_length ((MonoArray*)start);
-                       skip_size += sizeof (MonoArray);
+                       skip_size = safe_object_get_size ((MonoObject*)start);
                        skip_size += (ALLOC_ALIGN - 1);
                        skip_size &= ~(ALLOC_ALIGN - 1);
                        OBJ_VECTOR_FOREACH_PTR (vt, start);
@@ -1272,12 +1308,43 @@ scan_area (char *start, char *end)
                type_str, type_rlen, type_vector, type_bitmap, type_lbit, type_complex);*/
 }
 
+static gboolean
+need_remove_object_for_domain (char *start, MonoDomain *domain)
+{
+       GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
+       if (mono_object_domain (start) == domain) {
+               DEBUG (1, fprintf (gc_debug_file, "Need to cleanup object %p, (%s)\n", start, safe_name (start)));
+               return TRUE;
+       }
+       return FALSE;
+}
+
+static void
+process_object_for_domain_clearing (char *start, MonoDomain *domain)
+{
+       GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
+       /* The object could be a proxy for an object in the domain
+          we're deleting. */
+       if (mono_class_has_parent (vt->klass, mono_defaults.real_proxy_class)) {
+               MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server;
+
+               /* The server could already have been zeroed out, so
+                  we need to check for that, too. */
+               if (server && (!LOAD_VTABLE (server) || mono_object_domain (server) == domain)) {
+                       DEBUG (1, fprintf (gc_debug_file, "Cleaning up remote pointer in %p to object %p (%s)\n",
+                                       start, server, LOAD_VTABLE (server) ? safe_name (server) : "null"));
+                       ((MonoRealProxy*)start)->unwrapped_server = NULL;
+               }
+       }
+}
+
 static void __attribute__((noinline))
 scan_area_for_domain (MonoDomain *domain, char *start, char *end)
 {
        GCVTable *vt;
        size_t skip_size;
-       int type, remove;
+       int type;
+       gboolean remove;
        mword desc;
 
        while (start < end) {
@@ -1286,12 +1353,12 @@ scan_area_for_domain (MonoDomain *domain, char *start, char *end)
                        continue;
                }
                vt = (GCVTable*)LOAD_VTABLE (start);
-               /* handle threads someway (maybe insert the root domain vtable?) */
-               if (mono_object_domain (start) == domain && vt->klass != mono_defaults.thread_class) {
-                       DEBUG (1, fprintf (gc_debug_file, "Need to cleanup object %p, (%s)\n", start, safe_name (start)));
-                       remove = 1;
-               } else {
-                       remove = 0;
+               process_object_for_domain_clearing (start, domain);
+               remove = need_remove_object_for_domain (start, domain);
+               if (remove && ((MonoObject*)start)->synchronisation) {
+                       void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)start);
+                       if (dislink)
+                               mono_gc_register_disappearing_link (NULL, dislink, FALSE);
                }
                desc = vt->desc;
                type = desc & 0x7;
@@ -1307,9 +1374,7 @@ scan_area_for_domain (MonoDomain *domain, char *start, char *end)
                        start += skip_size;
                        continue;
                } else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
-                       skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
-                       skip_size *= mono_array_length ((MonoArray*)start);
-                       skip_size += sizeof (MonoArray);
+                       skip_size = safe_object_get_size ((MonoObject*)start);
                        skip_size += (ALLOC_ALIGN - 1);
                        skip_size &= ~(ALLOC_ALIGN - 1);
                        if (type == DESC_TYPE_ARRAY) {
@@ -1358,6 +1423,19 @@ scan_area_for_domain (MonoDomain *domain, char *start, char *end)
        }
 }
 
+static void
+clear_domain_process_pinned_object_callback (PinnedChunk *chunk, char *obj, size_t size, MonoDomain *domain)
+{
+       process_object_for_domain_clearing (obj, domain);
+}
+
+static void
+clear_domain_free_pinned_object_callback (PinnedChunk *chunk, char *obj, size_t size, MonoDomain *domain)
+{
+       if (need_remove_object_for_domain (obj, domain))
+               free_pinned_object (chunk, obj, size);
+}
+
 /*
  * When appdomains are unloaded we can easily remove objects that have finalizers,
  * but all the others could still be present in random places on the heap.
@@ -1371,14 +1449,58 @@ void
 mono_gc_clear_domain (MonoDomain * domain)
 {
        GCMemSection *section;
+       LOSObject *bigobj, *prev;
+       Fragment *frag;
+       int i;
+
        LOCK_GC;
+       /* Clear all remaining nursery fragments */
+       if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
+               g_assert (nursery_next <= nursery_frag_real_end);
+               memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
+               for (frag = nursery_fragments; frag; frag = frag->next) {
+                       memset (frag->fragment_start, 0, frag->fragment_end - frag->fragment_start);
+               }
+       }
+
        for (section = section_list; section; section = section->next) {
                scan_area_for_domain (domain, section->data, section->end_data);
        }
-       /* FIXME: handle big and fixed objects (we remove, don't clear in this case) */
+
+       /* We need two passes over pinned and large objects because
+          freeing such an object gives its memory back to the OS (in
+          the case of large objects) or obliterates its vtable
+          (pinned objects), but we might need to dereference a
+          pointer from an object to another object if the first
+          object is a proxy. */
+       scan_pinned_objects (clear_domain_process_pinned_object_callback, domain);
+       for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
+               process_object_for_domain_clearing (bigobj->data, domain);
+
+       prev = NULL;
+       for (bigobj = los_object_list; bigobj;) {
+               if (need_remove_object_for_domain (bigobj->data, domain)) {
+                       LOSObject *to_free = bigobj;
+                       if (prev)
+                               prev->next = bigobj->next;
+                       else
+                               los_object_list = bigobj->next;
+                       bigobj = bigobj->next;
+                       DEBUG (1, fprintf (gc_debug_file, "Freeing large object %p (%s)\n",
+                                       bigobj->data, safe_name (bigobj->data)));
+                       free_large_object (to_free);
+                       continue;
+               }
+               prev = bigobj;
+               bigobj = bigobj->next;
+       }
+       scan_pinned_objects (clear_domain_free_pinned_object_callback, domain);
+
+       for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
+               null_links_for_domain (domain, i);
+
        UNLOCK_GC;
 }
-#endif
 
 /*
  * add_to_global_remset:
@@ -2241,8 +2363,28 @@ scan_needed_big_objects (char *start_addr, char *end_addr)
        return count;
 }
 
+static DisappearingLinkHashTable*
+get_dislink_hash_table (int generation)
+{
+       switch (generation) {
+       case GENERATION_NURSERY: return &minor_disappearing_link_hash;
+       case GENERATION_OLD: return &major_disappearing_link_hash;
+       default: g_assert_not_reached ();
+       }
+}
+
+static FinalizeEntryHashTable*
+get_finalize_entry_hash_table (int generation)
+{
+       switch (generation) {
+       case GENERATION_NURSERY: return &minor_finalizable_hash;
+       case GENERATION_OLD: return &major_finalizable_hash;
+       default: g_assert_not_reached ();
+       }
+}
+
 static void
-finish_gray_stack (char *start_addr, char *end_addr)
+finish_gray_stack (char *start_addr, char *end_addr, int generation)
 {
        TV_DECLARE (atv);
        TV_DECLARE (btv);
@@ -2279,7 +2421,9 @@ finish_gray_stack (char *start_addr, char *end_addr)
         */
        do {
                fin_ready = num_ready_finalizers;
-               finalize_in_range (start_addr, end_addr);
+               finalize_in_range (start_addr, end_addr, generation);
+               if (generation == GENERATION_OLD)
+                       finalize_in_range (nursery_start, nursery_real_end, GENERATION_NURSERY);
                bigo_scanned_num = scan_needed_big_objects (start_addr, end_addr);
 
                /* drain the new stack that might have been created */
@@ -2302,7 +2446,7 @@ finish_gray_stack (char *start_addr, char *end_addr)
         * GC a finalized object my lose the monitor because it is cleared before the finalizer is
         * called.
         */
-       null_link_in_range (start_addr, end_addr);
+       null_link_in_range (start_addr, end_addr, generation);
        TV_GETTIME (btv);
        DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan: %d usecs\n", TV_ELAPSED (atv, btv)));
 }
@@ -2522,7 +2666,7 @@ collect_nursery (size_t requested_size)
        TV_GETTIME (btv);
        DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (atv, btv)));
 
-       finish_gray_stack (nursery_start, nursery_next);
+       finish_gray_stack (nursery_start, nursery_next, GENERATION_NURSERY);
 
        /* walk the pin_queue, build up the fragment list of free memory, unmark
         * pinned objects as we go, memzero() the empty fragments so they are ready for the
@@ -2676,7 +2820,8 @@ major_collection (void)
         */
        scan_needed_big_objects (heap_start, heap_end);
        /* all the objects in the heap */
-       finish_gray_stack (heap_start, heap_end);
+       finish_gray_stack (heap_start, heap_end, GENERATION_OLD);
+       null_link_in_range (nursery_start, nursery_real_end, GENERATION_NURSERY);
 
        /* sweep the big objects list */
        prevbo = NULL;
@@ -2963,7 +3108,7 @@ mark_pinned_from_addresses (PinnedChunk *chunk, void **start, void **end)
 }
 
 static void
-sweep_pinned_objects (void)
+scan_pinned_objects (void (*callback) (PinnedChunk*, char*, size_t, void*), void *callback_data)
 {
        PinnedChunk *chunk;
        int i, obj_size;
@@ -2972,7 +3117,7 @@ sweep_pinned_objects (void)
        void *end_chunk;
        for (chunk = pinned_chunk_list; chunk; chunk = chunk->next) {
                end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
-               DEBUG (6, fprintf (gc_debug_file, "Sweeping pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
+               DEBUG (6, fprintf (gc_debug_file, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
                for (i = 0; i < chunk->num_pages; ++i) {
                        obj_size = chunk->page_sizes [i];
                        if (!obj_size)
@@ -2984,15 +3129,8 @@ sweep_pinned_objects (void)
                                ptr = (void**)p;
                                DEBUG (9, fprintf (gc_debug_file, "Considering %p (vtable: %p)\n", ptr, *ptr));
                                /* if the first word (the vtable) is outside the chunk we have an object */
-                               if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk)) {
-                                       if (object_is_pinned (ptr)) {
-                                               unpin_object (ptr);
-                                               DEBUG (6, fprintf (gc_debug_file, "Unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
-                                       } else {
-                                               /* FIXME: add to freelist */
-                                               DEBUG (6, fprintf (gc_debug_file, "Going to free unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
-                                       }
-                               }
+                               if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk))
+                                       callback (chunk, (char*)ptr, obj_size, callback_data);
                                p += obj_size;
                        }
                }
@@ -3000,39 +3138,39 @@ sweep_pinned_objects (void)
 }
 
 static void
-scan_from_pinned_objects (char *addr_start, char *addr_end)
+sweep_pinned_objects_callback (PinnedChunk *chunk, char *ptr, size_t size, void *data)
 {
-       PinnedChunk *chunk;
-       int i, obj_size;
-       char *p, *endp;
-       void **ptr;
-       void *end_chunk;
-       for (chunk = pinned_chunk_list; chunk; chunk = chunk->next) {
-               end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
-               DEBUG (6, fprintf (gc_debug_file, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
-               for (i = 0; i < chunk->num_pages; ++i) {
-                       obj_size = chunk->page_sizes [i];
-                       if (!obj_size)
-                               continue;
-                       p = i? (char*)chunk + i * FREELIST_PAGESIZE: chunk->start_data;
-                       endp = i? p + FREELIST_PAGESIZE: (char*)chunk + FREELIST_PAGESIZE;
-                       DEBUG (6, fprintf (gc_debug_file, "Page %d (size: %d, range: %p-%p)\n", i, obj_size, p, endp));
-                       while (p + obj_size <= endp) {
-                               ptr = (void**)p;
-                               DEBUG (9, fprintf (gc_debug_file, "Considering %p (vtable: %p)\n", ptr, *ptr));
-                               /* if the first word (the vtable) is outside the chunk we have an object */
-                               if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk)) {
-                                       DEBUG (6, fprintf (gc_debug_file, "Precise object scan %d of alloc_pinned %p (%s)\n", i, ptr, safe_name (ptr)));
-                                       // FIXME: Put objects without references into separate chunks
-                                       // which do not need to be scanned
-                                       scan_object ((char*)ptr, addr_start, addr_end);
-                               }
-                               p += obj_size;
-                       }
-               }
+       if (object_is_pinned (ptr)) {
+               unpin_object (ptr);
+               DEBUG (6, fprintf (gc_debug_file, "Unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
+       } else {
+               DEBUG (6, fprintf (gc_debug_file, "Freeing unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
+               free_pinned_object (chunk, ptr, size);
        }
 }
 
+static void
+sweep_pinned_objects (void)
+{
+       scan_pinned_objects (sweep_pinned_objects_callback, NULL);
+}
+
+static void
+scan_object_callback (PinnedChunk *chunk, char *ptr, size_t size, char **data)
+{
+       DEBUG (6, fprintf (gc_debug_file, "Precise object scan of alloc_pinned %p (%s)\n", ptr, safe_name (ptr)));
+       /* FIXME: Put objects without references into separate chunks
+          which do not need to be scanned */
+       scan_object (ptr, data [0], data [1]);
+}
+
+static void
+scan_from_pinned_objects (char *addr_start, char *addr_end)
+{
+       char *data [2] = { addr_start, addr_end };
+       scan_pinned_objects (scan_object_callback, data);
+}
+
 /*
  * Find the slot number in the freelist for memory chunks that
  * can contain @size objects.
@@ -3596,18 +3734,59 @@ queue_finalization_entry (FinalizeEntry *entry) {
        }
 }
 
+/* LOCKING: requires that the GC lock is held */
 static void
-finalize_in_range (char *start, char *end)
+rehash_fin_table (FinalizeEntryHashTable *hash_table)
 {
+       FinalizeEntry **finalizable_hash = hash_table->table;
+       mword finalizable_hash_size = hash_table->size;
+       int i;
+       unsigned int hash;
+       FinalizeEntry **new_hash;
+       FinalizeEntry *entry, *next;
+       int new_size = g_spaced_primes_closest (hash_table->num_registered);
+
+       new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*));
+       for (i = 0; i < finalizable_hash_size; ++i) {
+               for (entry = finalizable_hash [i]; entry; entry = next) {
+                       hash = mono_object_hash (entry->object) % new_size;
+                       next = entry->next;
+                       entry->next = new_hash [hash];
+                       new_hash [hash] = entry;
+               }
+       }
+       free_internal_mem (finalizable_hash);
+       hash_table->table = new_hash;
+       hash_table->size = new_size;
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+rehash_fin_table_if_necessary (FinalizeEntryHashTable *hash_table)
+{
+       if (hash_table->num_registered >= hash_table->size * 2)
+               rehash_fin_table (hash_table);
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+finalize_in_range (char *start, char *end, int generation)
+{
+       FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
        FinalizeEntry *entry, *prev;
        int i;
+       FinalizeEntry **finalizable_hash = hash_table->table;
+       mword finalizable_hash_size = hash_table->size;
+
        if (no_finalize)
                return;
        for (i = 0; i < finalizable_hash_size; ++i) {
                prev = NULL;
                for (entry = finalizable_hash [i]; entry;) {
                        if ((char*)entry->object >= start && (char*)entry->object < end && ((char*)entry->object < to_space || (char*)entry->object >= to_space_end)) {
-                               if (object_is_fin_ready (entry->object)) {
+                               gboolean is_fin_ready = object_is_fin_ready (entry->object);
+                               char *copy = copy_object (entry->object, start, end);
+                               if (is_fin_ready) {
                                        char *from;
                                        FinalizeEntry *next;
                                        /* remove and put in fin_ready_list */
@@ -3617,18 +3796,45 @@ finalize_in_range (char *start, char *end)
                                                finalizable_hash [i] = entry->next;
                                        next = entry->next;
                                        num_ready_finalizers++;
-                                       num_registered_finalizers--;
+                                       hash_table->num_registered--;
                                        queue_finalization_entry (entry);
                                        /* Make it survive */
                                        from = entry->object;
-                                       entry->object = copy_object (entry->object, start, end);
-                                       DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, num_registered_finalizers));
+                                       entry->object = copy;
+                                       DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, hash_table->num_registered));
                                        entry = next;
                                        continue;
                                } else {
-                                       /* update pointer */
-                                       DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s)\n", entry->object, safe_name (entry->object)));
-                                       entry->object = copy_object (entry->object, start, end);
+                                       char *from = entry->object;
+                                       if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
+                                               FinalizeEntry *next = entry->next;
+                                               unsigned int major_hash;
+                                               /* remove from the list */
+                                               if (prev)
+                                                       prev->next = entry->next;
+                                               else
+                                                       finalizable_hash [i] = entry->next;
+                                               hash_table->num_registered--;
+
+                                               entry->object = copy;
+
+                                               /* insert it into the major hash */
+                                               rehash_fin_table_if_necessary (&major_finalizable_hash);
+                                               major_hash = mono_object_hash ((MonoObject*) copy) %
+                                                       major_finalizable_hash.size;
+                                               entry->next = major_finalizable_hash.table [major_hash];
+                                               major_finalizable_hash.table [major_hash] = entry;
+                                               major_finalizable_hash.num_registered++;
+
+                                               DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from));
+
+                                               entry = next;
+                                               continue;
+                                       } else {
+                                               /* update pointer */
+                                               DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from));
+                                               entry->object = copy;
+                                       }
                                }
                        }
                        prev = entry;
@@ -3637,17 +3843,24 @@ finalize_in_range (char *start, char *end)
        }
 }
 
+/* LOCKING: requires that the GC lock is held */
 static void
-null_link_in_range (char *start, char *end)
+null_link_in_range (char *start, char *end, int generation)
 {
+       DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
+       DisappearingLink **disappearing_link_hash = hash->table;
+       int disappearing_link_hash_size = hash->size;
        DisappearingLink *entry, *prev;
        int i;
+       if (!hash->num_links)
+               return;
        for (i = 0; i < disappearing_link_hash_size; ++i) {
                prev = NULL;
                for (entry = disappearing_link_hash [i]; entry;) {
                        char *object = DISLINK_OBJECT (entry);
                        if (object >= start && object < end && (object < to_space || object >= to_space_end)) {
-                               if (!DISLINK_TRACK (entry) && object_is_fin_ready (object)) {
+                               gboolean track = DISLINK_TRACK (entry);
+                               if (!track && object_is_fin_ready (object)) {
                                        void **p = entry->link;
                                        DisappearingLink *old;
                                        *p = NULL;
@@ -3660,21 +3873,49 @@ null_link_in_range (char *start, char *end)
                                        old = entry->next;
                                        free_internal_mem (entry);
                                        entry = old;
-                                       num_disappearing_links--;
+                                       hash->num_links--;
                                        continue;
                                } else {
-                                       /* update pointer if it's moved
+                                       char *copy = copy_object (object, start, end);
+
+                                       /* Update pointer if it's moved.  If the object
+                                        * has been moved out of the nursery, we need to
+                                        * remove the link from the minor hash table to
+                                        * the major one.
+                                        *
                                         * FIXME: what if an object is moved earlier?
                                         */
-                                       /* We set the track
-                                        * resurrection bit to FALSE
-                                        * here so that the object can
-                                        * be collected in the next
-                                        * cycle (i.e. after it was
-                                        * finalized).
-                                        */
-                                       *entry->link = HIDE_POINTER (copy_object (object, start, end), FALSE);
-                                       DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
+
+                                       if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
+                                               void **link = entry->link;
+                                               DisappearingLink *old;
+                                               /* remove from list */
+                                               if (prev)
+                                                       prev->next = entry->next;
+                                               else
+                                                       disappearing_link_hash [i] = entry->next;
+                                               old = entry->next;
+                                               free_internal_mem (entry);
+                                               entry = old;
+                                               hash->num_links--;
+
+                                               add_or_remove_disappearing_link ((MonoObject*)copy, link, track,
+                                                       &major_disappearing_link_hash);
+
+                                               DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy));
+
+                                               continue;
+                                       } else {
+                                               /* We set the track resurrection bit to
+                                                * FALSE if the object is to be finalized
+                                                * so that the object can be collected in
+                                                * the next cycle (i.e. after it was
+                                                * finalized).
+                                                */
+                                               *entry->link = HIDE_POINTER (copy,
+                                                       object_is_fin_ready (object) ? FALSE : track);
+                                               DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
+                                       }
                                }
                        }
                        prev = entry;
@@ -3683,28 +3924,68 @@ null_link_in_range (char *start, char *end)
        }
 }
 
-/**
- * mono_gc_finalizers_for_domain:
- * @domain: the unloading appdomain
- * @out_array: output array
- * @out_size: size of output array
- *
- * Store inside @out_array up to @out_size objects that belong to the unloading
- * appdomain @domain. Returns the number of stored items. Can be called repeteadly
- * until it returns 0.
- * The items are removed from the finalizer data structure, so the caller is supposed
- * to finalize them.
- * @out_array should be on the stack to allow the GC to know the objects are still alive.
- */
-int
-mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
+static const char*
+dislink_table_name (DisappearingLinkHashTable *table)
+{
+       if (table == &minor_disappearing_link_hash)
+               return "minor table";
+       return "major table";
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+null_links_for_domain (MonoDomain *domain, int generation)
+{
+       DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
+       DisappearingLink **disappearing_link_hash = hash->table;
+       int disappearing_link_hash_size = hash->size;
+       DisappearingLink *entry, *prev;
+       int i;
+       for (i = 0; i < disappearing_link_hash_size; ++i) {
+               prev = NULL;
+               for (entry = disappearing_link_hash [i]; entry; ) {
+                       char *object = DISLINK_OBJECT (entry);
+                       /* FIXME: actually there should be no object
+                          left in the domain with a non-null vtable
+                          (provided we remove the Thread special
+                          case) */
+                       if (object && (!((MonoObject*)object)->vtable || mono_object_domain (object) == domain)) {
+                               DisappearingLink *next = entry->next;
+
+                               if (prev)
+                                       prev->next = next;
+                               else
+                                       disappearing_link_hash [i] = next;
+
+                               if (*(entry->link)) {
+                                       *(entry->link) = NULL;
+                                       g_warning ("Disappearing link %p not freed", entry->link);
+                               } else {
+                                       free_internal_mem (entry);
+                               }
+
+                               entry = next;
+                               continue;
+                       }
+                       prev = entry;
+                       entry = entry->next;
+               }
+       }
+}
+
+/* LOCKING: requires that the GC lock is held */
+static int
+finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
+       FinalizeEntryHashTable *hash_table)
 {
+       FinalizeEntry **finalizable_hash = hash_table->table;
+       mword finalizable_hash_size = hash_table->size;
        FinalizeEntry *entry, *prev;
        int i, count;
+
        if (no_finalize || !out_size || !out_array)
                return 0;
        count = 0;
-       LOCK_GC;
        for (i = 0; i < finalizable_hash_size; ++i) {
                prev = NULL;
                for (entry = finalizable_hash [i]; entry;) {
@@ -3716,50 +3997,55 @@ mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int o
                                else
                                        finalizable_hash [i] = entry->next;
                                next = entry->next;
-                               num_registered_finalizers--;
+                               hash_table->num_registered--;
                                out_array [count ++] = entry->object;
-                               DEBUG (5, fprintf (gc_debug_file, "Collecting object for finalization: %p (%s) (%d/%d)\n", entry->object, safe_name (entry->object), num_ready_finalizers, num_registered_finalizers));
+                               DEBUG (5, fprintf (gc_debug_file, "Collecting object for finalization: %p (%s) (%d/%d)\n", entry->object, safe_name (entry->object), num_ready_finalizers, hash_table->num_registered));
                                entry = next;
-                               if (count == out_size) {
-                                       UNLOCK_GC;
+                               if (count == out_size)
                                        return count;
-                               }
                                continue;
                        }
                        prev = entry;
                        entry = entry->next;
                }
        }
-       UNLOCK_GC;
        return count;
 }
 
-static void
-rehash_fin_table (void)
+/**
+ * mono_gc_finalizers_for_domain:
+ * @domain: the unloading appdomain
+ * @out_array: output array
+ * @out_size: size of output array
+ *
+ * Store inside @out_array up to @out_size objects that belong to the unloading
+ * appdomain @domain. Returns the number of stored items. Can be called repeteadly
+ * until it returns 0.
+ * The items are removed from the finalizer data structure, so the caller is supposed
+ * to finalize them.
+ * @out_array should be on the stack to allow the GC to know the objects are still alive.
+ */
+int
+mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
 {
-       int i;
-       unsigned int hash;
-       FinalizeEntry **new_hash;
-       FinalizeEntry *entry, *next;
-       int new_size = g_spaced_primes_closest (num_registered_finalizers);
+       int result;
 
-       new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*));
-       for (i = 0; i < finalizable_hash_size; ++i) {
-               for (entry = finalizable_hash [i]; entry; entry = next) {
-                       hash = mono_object_hash (entry->object) % new_size;
-                       next = entry->next;
-                       entry->next = new_hash [hash];
-                       new_hash [hash] = entry;
-               }
+       LOCK_GC;
+       result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
+       if (result < out_size) {
+               result += finalizers_for_domain (domain, out_array + result, out_size - result,
+                       &major_finalizable_hash);
        }
-       free_internal_mem (finalizable_hash);
-       finalizable_hash = new_hash;
-       finalizable_hash_size = new_size;
+       UNLOCK_GC;
+
+       return result;
 }
 
-void
-mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
+static void
+register_for_finalization (MonoObject *obj, void *user_data, FinalizeEntryHashTable *hash_table)
 {
+       FinalizeEntry **finalizable_hash;
+       mword finalizable_hash_size;
        FinalizeEntry *entry, *prev;
        unsigned int hash;
        if (no_finalize)
@@ -3767,8 +4053,9 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
        g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
        hash = mono_object_hash (obj);
        LOCK_GC;
-       if (num_registered_finalizers >= finalizable_hash_size * 2)
-               rehash_fin_table ();
+       rehash_fin_table_if_necessary (hash_table);
+       finalizable_hash = hash_table->table;
+       finalizable_hash_size = hash_table->size;
        hash %= finalizable_hash_size;
        prev = NULL;
        for (entry = finalizable_hash [hash]; entry; entry = entry->next) {
@@ -3779,8 +4066,8 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
                                        prev->next = entry->next;
                                else
                                        finalizable_hash [hash] = entry->next;
-                               num_registered_finalizers--;
-                               DEBUG (5, fprintf (gc_debug_file, "Removed finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, num_registered_finalizers));
+                               hash_table->num_registered--;
+                               DEBUG (5, fprintf (gc_debug_file, "Removed finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered));
                                free_internal_mem (entry);
                        }
                        UNLOCK_GC;
@@ -3797,19 +4084,30 @@ mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
        entry->object = obj;
        entry->next = finalizable_hash [hash];
        finalizable_hash [hash] = entry;
-       num_registered_finalizers++;
-       DEBUG (5, fprintf (gc_debug_file, "Added finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, num_registered_finalizers));
+       hash_table->num_registered++;
+       DEBUG (5, fprintf (gc_debug_file, "Added finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered));
        UNLOCK_GC;
 }
 
+void
+mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
+{
+       if (ptr_in_nursery (obj))
+               register_for_finalization (obj, user_data, &minor_finalizable_hash);
+       else
+               register_for_finalization (obj, user_data, &minor_finalizable_hash);
+}
+
 static void
-rehash_dislink (void)
+rehash_dislink (DisappearingLinkHashTable *hash_table)
 {
+       DisappearingLink **disappearing_link_hash = hash_table->table;
+       int disappearing_link_hash_size = hash_table->size;
        int i;
        unsigned int hash;
        DisappearingLink **new_hash;
        DisappearingLink *entry, *next;
-       int new_size = g_spaced_primes_closest (num_disappearing_links);
+       int new_size = g_spaced_primes_closest (hash_table->num_links);
 
        new_hash = get_internal_mem (new_size * sizeof (DisappearingLink*));
        for (i = 0; i < disappearing_link_hash_size; ++i) {
@@ -3821,19 +4119,25 @@ rehash_dislink (void)
                }
        }
        free_internal_mem (disappearing_link_hash);
-       disappearing_link_hash = new_hash;
-       disappearing_link_hash_size = new_size;
+       hash_table->table = new_hash;
+       hash_table->size = new_size;
 }
 
+/* LOCKING: assumes the GC lock is held */
 static void
-mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
+add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track,
+       DisappearingLinkHashTable *hash_table)
 {
        DisappearingLink *entry, *prev;
        unsigned int hash;
-       LOCK_GC;
+       DisappearingLink **disappearing_link_hash = hash_table->table;
+       int disappearing_link_hash_size = hash_table->size;
 
-       if (num_disappearing_links >= disappearing_link_hash_size * 2)
-               rehash_dislink ();
+       if (hash_table->num_links >= disappearing_link_hash_size * 2) {
+               rehash_dislink (hash_table);
+               disappearing_link_hash = hash_table->table;
+               disappearing_link_hash_size = hash_table->size;
+       }
        /* FIXME: add check that link is not in the heap */
        hash = mono_aligned_addr_hash (link) % disappearing_link_hash_size;
        entry = disappearing_link_hash [hash];
@@ -3847,26 +4151,40 @@ mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track
                                        prev->next = entry->next;
                                else
                                        disappearing_link_hash [hash] = entry->next;
-                               num_disappearing_links--;
-                               DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d)\n", entry, num_disappearing_links));
+                               hash_table->num_links--;
+                               DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d) from %s\n", entry, hash_table->num_links, dislink_table_name (hash_table)));
                                free_internal_mem (entry);
                                *link = NULL;
                        } else {
                                *link = HIDE_POINTER (obj, track); /* we allow the change of object */
                        }
-                       UNLOCK_GC;
                        return;
                }
                prev = entry;
        }
+       if (obj == NULL)
+               return;
        entry = get_internal_mem (sizeof (DisappearingLink));
        *link = HIDE_POINTER (obj, track);
        entry->link = link;
        entry->next = disappearing_link_hash [hash];
        disappearing_link_hash [hash] = entry;
-       num_disappearing_links++;
-       DEBUG (5, fprintf (gc_debug_file, "Added dislink %p for object: %p (%s) at %p\n", entry, obj, obj->vtable->klass->name, link));
-       UNLOCK_GC;
+       hash_table->num_links++;
+       DEBUG (5, fprintf (gc_debug_file, "Added dislink %p for object: %p (%s) at %p to %s\n", entry, obj, obj->vtable->klass->name, link, dislink_table_name (hash_table)));
+}
+
+/* LOCKING: assumes the GC lock is held */
+static void
+mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
+{
+       add_or_remove_disappearing_link (NULL, link, FALSE, &minor_disappearing_link_hash);
+       add_or_remove_disappearing_link (NULL, link, FALSE, &major_disappearing_link_hash);
+       if (obj) {
+               if (ptr_in_nursery (obj))
+                       add_or_remove_disappearing_link (obj, link, track, &minor_disappearing_link_hash);
+               else
+                       add_or_remove_disappearing_link (obj, link, track, &major_disappearing_link_hash);
+       }
 }
 
 int
@@ -5180,9 +5498,7 @@ check_remsets_for_area (char *start, char *end)
                        type_rlen++;
                        continue;
                } else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
-                       skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
-                       skip_size *= mono_array_length ((MonoArray*)start);
-                       skip_size += sizeof (MonoArray);
+                       skip_size = safe_object_get_size ((MonoObject*)start);
                        skip_size += (ALLOC_ALIGN - 1);
                        skip_size &= ~(ALLOC_ALIGN - 1);
                        OBJ_VECTOR_FOREACH_PTR (vt, start);
@@ -5432,13 +5748,17 @@ mono_gc_enable_events (void)
 void
 mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
 {
+       LOCK_GC;
        mono_gc_register_disappearing_link (obj, link_addr, track);
+       UNLOCK_GC;
 }
 
 void
 mono_gc_weak_link_remove (void **link_addr)
 {
+       LOCK_GC;
        mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
+       UNLOCK_GC;
 }
 
 MonoObject*