| // natReference.cc - Native code for References | |
| /* Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation | |
| This file is part of libgcj. | |
| This software is copyrighted work licensed under the terms of the | |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for | |
| details. */ | |
| // Written by Tom Tromey <tromey@redhat.com> | |
| #include <config.h> | |
| #include <gcj/cni.h> | |
| #include <jvm.h> | |
| #include <java/lang/Throwable.h> | |
| #include <java/lang/ref/Reference.h> | |
| #include <java/lang/ref/SoftReference.h> | |
| #include <java/lang/ref/WeakReference.h> | |
| #include <java/lang/ref/PhantomReference.h> | |
| #include <java/lang/ref/ReferenceQueue.h> | |
| static void finalize_reference (jobject ref); | |
| static void finalize_referred_to_object (jobject obj); | |
| enum weight | |
| { | |
| SOFT = 0, | |
| WEAK = 1, | |
| FINALIZE = 2, | |
| PHANTOM = 3, | |
| // This is used to mark the head of a list. | |
| HEAD = 4, | |
| // This is used to mark a deleted item. | |
| DELETED = 5 | |
| }; | |
| // Objects of this type are used in the hash table to keep track of | |
| // the mapping between a finalizable object and the various References | |
| // which refer to it. | |
| struct object_list | |
| { | |
| // The reference object. This is NULL for FINALIZE weight. | |
| jobject reference; | |
| // The weight of this object. | |
| enum weight weight; | |
| // Next in list. | |
| object_list *next; | |
| }; | |
| // Hash table used to hold mapping from object to References. The | |
| // object_list item in the hash holds the object itself in the | |
| // reference field; chained to it are all the references sorted in | |
| // order of weight (lowest first). | |
| static object_list *hash = NULL; | |
| // Number of slots used in HASH. | |
| static int hash_count = 0; | |
| // Number of slots total in HASH. Must be power of 2. | |
| static int hash_size = 0; | |
| #define DELETED_REFERENCE ((jobject) -1) | |
| static object_list * | |
| find_slot (jobject key) | |
| { | |
| jint hcode = _Jv_HashCode (key); | |
| /* step must be non-zero, and relatively prime with hash_size. */ | |
| jint step = (hcode ^ (hcode >> 16)) | 1; | |
| int start_index = hcode & (hash_size - 1); | |
| int index = start_index; | |
| int deleted_index = -1; | |
| do | |
| { | |
| object_list *ptr = &hash[index]; | |
| if (ptr->reference == key) | |
| return ptr; | |
| else if (ptr->reference == NULL) | |
| { | |
| if (deleted_index == -1) | |
| return ptr; | |
| else | |
| return &hash[deleted_index]; | |
| } | |
| else if (ptr->weight == DELETED) | |
| { | |
| deleted_index = index; | |
| JvAssert (ptr->reference == DELETED_REFERENCE); | |
| } | |
| index = (index + step) & (hash_size - 1); | |
| } | |
| while (index != start_index); | |
| // Note that we can have INDEX == START_INDEX if the table has no | |
| // NULL entries but does have DELETED entries. | |
| JvAssert (deleted_index >= 0); | |
| return &hash[deleted_index]; | |
| } | |
| static void | |
| rehash () | |
| { | |
| if (hash == NULL) | |
| { | |
| hash_size = 1024; | |
| hash = (object_list *) _Jv_Malloc (hash_size * sizeof (object_list)); | |
| memset (hash, 0, hash_size * sizeof (object_list)); | |
| } | |
| else | |
| { | |
| object_list *old = hash; | |
| int i = hash_size; | |
| hash_size *= 2; | |
| hash = (object_list *) _Jv_Malloc (hash_size * sizeof (object_list)); | |
| memset (hash, 0, hash_size * sizeof (object_list)); | |
| while (--i >= 0) | |
| { | |
| if (old[i].reference == NULL || old[i].weight == DELETED) | |
| continue; | |
| object_list *newslot = find_slot (old[i].reference); | |
| *newslot = old[i]; | |
| } | |
| _Jv_Free (old); | |
| } | |
| } | |
| // Remove a Reference. | |
| static void | |
| remove_from_hash (jobject obj) | |
| { | |
| java::lang::ref::Reference *ref | |
| = reinterpret_cast<java::lang::ref::Reference *> (obj); | |
| object_list *head = find_slot (ref->copy); | |
| // We might have found a new slot. We can just ignore that here. | |
| if (head->reference != ref->copy) | |
| return; | |
| object_list **link = &head->next; | |
| head = head->next; | |
| while (head && head->reference != ref) | |
| { | |
| link = &head->next; | |
| head = head->next; | |
| } | |
| // Remove the slot. | |
| if (head) | |
| { | |
| *link = head->next; | |
| _Jv_Free (head); | |
| } | |
| } | |
| // Return list head if object is in hash, NULL otherwise. | |
| object_list * | |
| in_hash (jobject obj) | |
| { | |
| // The hash table might not yet be initialized. | |
| if (hash == NULL) | |
| return NULL; | |
| object_list *head = find_slot (obj); | |
| if (head->reference != obj) | |
| return NULL; | |
| return head; | |
| } | |
| // FIXME what happens if an object's finalizer creates a Reference to | |
| // the object, and the object has never before been added to the hash? | |
| // Madness! | |
| // Add an item to the hash table. If the item is new, we also add a | |
| // finalizer item. We keep items in the hash table until they are | |
| // completely collected; this lets us know when an item is new, even | |
| // if it has been resurrected after its finalizer has been run. | |
| static void | |
| add_to_hash (java::lang::ref::Reference *the_reference) | |
| { | |
| JvSynchronize sync (java::lang::ref::Reference::lock); | |
| if (3 * hash_count >= 2 * hash_size) | |
| rehash (); | |
| // Use `copy' here because the `referent' field has been cleared. | |
| jobject referent = the_reference->copy; | |
| object_list *item = find_slot (referent); | |
| if (item->reference == NULL || item->reference == DELETED_REFERENCE) | |
| { | |
| // New item, so make an entry for the finalizer. | |
| item->reference = referent; | |
| item->weight = HEAD; | |
| item->next = (object_list *) _Jv_Malloc (sizeof (object_list)); | |
| item->next->reference = NULL; | |
| item->next->weight = FINALIZE; | |
| item->next->next = NULL; | |
| ++hash_count; | |
| } | |
| object_list *n = (object_list *) _Jv_Malloc (sizeof (object_list)); | |
| n->reference = the_reference; | |
| enum weight w = PHANTOM; | |
| if (java::lang::ref::SoftReference::class$.isInstance (the_reference)) | |
| w = SOFT; | |
| else if (java::lang::ref::WeakReference::class$.isInstance (the_reference)) | |
| w = WEAK; | |
| n->weight = w; | |
| object_list **link = &item->next; | |
| object_list *iter = *link; | |
| while (iter && iter->weight < n->weight) | |
| { | |
| link = &iter->next; | |
| iter = *link; | |
| } | |
| n->next = *link; | |
| *link = n; | |
| } | |
| // Add a FINALIZE entry if one doesn't exist. | |
| static void | |
| maybe_add_finalize (object_list *entry, jobject obj) | |
| { | |
| object_list **link = &entry->next; | |
| object_list *iter = *link; | |
| while (iter && iter->weight < FINALIZE) | |
| { | |
| link = &iter->next; | |
| iter = *link; | |
| } | |
| // We want at most one FINALIZE entry in the queue. | |
| if (iter && iter->weight == FINALIZE) | |
| return; | |
| object_list *n = (object_list *) _Jv_Malloc (sizeof (object_list)); | |
| n->reference = obj; | |
| n->weight = FINALIZE; | |
| n->next = *link; | |
| *link = n; | |
| } | |
| // This is called when an object is ready to be finalized. This | |
| // actually implements the appropriate Reference semantics. | |
| static void | |
| finalize_referred_to_object (jobject obj) | |
| { | |
| JvSynchronize sync (java::lang::ref::Reference::lock); | |
| object_list *list = find_slot (obj); | |
| object_list *head = list->next; | |
| if (head == NULL) | |
| { | |
| // We have a truly dead object: the object's finalizer has been | |
| // run, all the object's references have been processed, and the | |
| // object is unreachable. There is, at long last, no way to | |
| // resurrect it. | |
| list->reference = DELETED_REFERENCE; | |
| list->weight = DELETED; | |
| --hash_count; | |
| return; | |
| } | |
| enum weight w = head->weight; | |
| if (w == FINALIZE) | |
| { | |
| // Update the list first, as _Jv_FinalizeString might end up | |
| // looking at this data structure. | |
| list->next = head->next; | |
| _Jv_Free (head); | |
| // If we have a Reference A to a Reference B, and B is | |
| // finalized, then we have to take special care to make sure | |
| // that B is properly deregistered. This is super gross. FIXME | |
| // will it fail if B's finalizer resurrects B? | |
| if (java::lang::ref::Reference::class$.isInstance (obj)) | |
| finalize_reference (obj); | |
| else if (obj->getClass() == &java::lang::String::class$) | |
| _Jv_FinalizeString (obj); | |
| else | |
| _Jv_FinalizeObject (obj); | |
| } | |
| else if (w != SOFT || _Jv_GCCanReclaimSoftReference (obj)) | |
| { | |
| // If we just decided to reclaim a soft reference, we might as | |
| // well do all the weak references at the same time. | |
| if (w == SOFT) | |
| w = WEAK; | |
| while (head && head->weight <= w) | |
| { | |
| java::lang::ref::Reference *ref | |
| = reinterpret_cast<java::lang::ref::Reference *> (head->reference); | |
| if (! ref->cleared) | |
| ref->enqueue (); | |
| object_list *next = head->next; | |
| _Jv_Free (head); | |
| head = next; | |
| } | |
| list->next = head; | |
| } | |
| // Re-register this finalizer. We always re-register because we | |
| // can't know until the next collection cycle whether or not the | |
| // object is truly unreachable. | |
| _Jv_RegisterFinalizer (obj, finalize_referred_to_object); | |
| } | |
| // This is called when a Reference object is finalized. If there is a | |
| // Reference pointing to this Reference then that case is handled by | |
| // finalize_referred_to_object. | |
| static void | |
| finalize_reference (jobject ref) | |
| { | |
| JvSynchronize sync (java::lang::ref::Reference::lock); | |
| remove_from_hash (ref); | |
| // The user might have a subclass of Reference with a finalizer. | |
| _Jv_FinalizeObject (ref); | |
| } | |
| void | |
| _Jv_RegisterStringFinalizer (jobject str) | |
| { | |
| // This function might be called before any other Reference method, | |
| // so we must ensure the class is initialized. | |
| _Jv_InitClass (&java::lang::ref::Reference::class$); | |
| JvSynchronize sync (java::lang::ref::Reference::lock); | |
| // If the object is in our hash table, then we might need to add a | |
| // new FINALIZE entry. Otherwise, we just register an ordinary | |
| // finalizer. | |
| object_list *entry = in_hash (str); | |
| if (entry) | |
| maybe_add_finalize (entry, str); | |
| else | |
| _Jv_RegisterFinalizer ((void *) str, _Jv_FinalizeString); | |
| } | |
| void | |
| ::java::lang::ref::Reference::create (jobject ref) | |
| { | |
| // Nothing says you can't make a Reference with a NULL referent. | |
| // But there's nothing to do in such a case. | |
| referent = reinterpret_cast<gnu::gcj::RawData *> (ref); | |
| copy = referent; | |
| if (referent != NULL) | |
| { | |
| JvSynchronize sync (java::lang::ref::Reference::lock); | |
| // `this' is a new Reference object. We register a new | |
| // finalizer for pointed-to object and we arrange a special | |
| // finalizer for ourselves as well. | |
| _Jv_RegisterFinalizer (this, finalize_reference); | |
| _Jv_RegisterFinalizer (referent, finalize_referred_to_object); | |
| gnu::gcj::RawData **p = &referent; | |
| _Jv_GCRegisterDisappearingLink ((jobject *) p); | |
| add_to_hash (this); | |
| } | |
| } |