From 45a5a8ba6662961d99f3a1a0db27a41afad6fa31 Mon Sep 17 00:00:00 2001 From: Pavel Afremov Date: Tue, 5 Jun 2007 21:03:37 +0400 Subject: [PATCH] Make interned string collectable by GC. Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redesign previous patch to use compressed references when it’s required, because current implementation of GC doesn’t support enumeration of uncompressed reference if reference compression is turned on. --- vm/tests/smoke/stress/Intern.java | 37 +++++ vm/vmcore/include/environment.h | 5 + vm/vmcore/include/vm_strings.h | 14 -- vm/vmcore/src/class_support/String_Pool.cpp | 39 +++-- vm/vmcore/src/init/vm_init.cpp | 4 + .../org/apache/harmony/kernel/vm/InternMap.java | 155 +++++++++++++++++++++++ .../javasrc/org/apache/harmony/kernel/vm/VM.java | 16 ++ .../native/org_apache_harmony_kernel_vm_VM.cpp | 7 - vm/vmcore/src/util/vm_strings.cpp | 13 -- 9 files changed, 238 insertions(+), 52 deletions(-) diff --git a/vm/tests/smoke/stress/Intern.java b/vm/tests/smoke/stress/Intern.java new file mode 100644 index 0000000..84c4667 --- /dev/null +++ b/vm/tests/smoke/stress/Intern.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package stress; + +/** + * Tests the correctness of string interning. + * + * @keyword XXX_stress + */ +public class Intern { + public static void main(String[] args) { + String s = "abc"; + for (int i = 0; i < 100000; i++) { + s = (s + i + s).intern(); + if (s.length() > 65536) s = "abc" + i; + if (i % 1000 == 0) trace("."); + } + } + + public static void trace(Object o) { + System.out.print(o); + System.out.flush(); + } +} diff --git a/vm/vmcore/include/environment.h b/vm/vmcore/include/environment.h index 9ff07ff..1d7b7d7 100644 --- a/vm/vmcore/include/environment.h +++ b/vm/vmcore/include/environment.h @@ -157,6 +157,11 @@ struct Global_Env { String* InitCauseDescriptor_String; /** + * Preloaded methods + */ + Method* VM_intern; + + /** * Preloaded classes */ Class* Boolean_Class; diff --git a/vm/vmcore/include/vm_strings.h b/vm/vmcore/include/vm_strings.h index f1657a2..0395b0c 100644 --- a/vm/vmcore/include/vm_strings.h +++ b/vm/vmcore/include/vm_strings.h @@ -33,20 +33,6 @@ #include "String_Pool.h" * Exported functons. */ -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Interns a string. - */ -VMEXPORT jstring -string_intern(JNIEnv*, jobject string); - -#ifdef __cplusplus -} -#endif - VMEXPORT // temporary solution for interpreter unplug Java_java_lang_String *vm_instantiate_cp_string_resolved(String*); diff --git a/vm/vmcore/src/class_support/String_Pool.cpp b/vm/vmcore/src/class_support/String_Pool.cpp index 362b011..fe83783 100644 --- a/vm/vmcore/src/class_support/String_Pool.cpp +++ b/vm/vmcore/src/class_support/String_Pool.cpp @@ -19,29 +19,26 @@ * @version $Revision: 1.1.2.1.4.4 $ */ -#include "platform_lowlevel.h" - -//MVM -#include -using namespace std; - #include #include #include #include #include -#include "String_Pool.h" -#include "environment.h" #include "open/hythread.h" #include "open/vm_util.h" #include "open/gc.h" + +#include "platform_lowlevel.h" +#include "String_Pool.h" +#include "environment.h" #include "atomics.h" #include "vm_strings.h" #include "vm_stats.h" +#include "ini.h" +#include "exceptions.h" + -#define LOG_DOMIAN "vm.strings" -#include "cxxlog.h" // apr_atomic_casptr should result in an .acq on IPF. void String_Pool::lock_pool () { @@ -323,19 +320,30 @@ void String_Pool::register_interned_stri // NOTE: it is safe to call this function in multiple threads BUT // don't iterate through interned strings while other threads do interning ManagedObject * String_Pool::intern(String * str) { + jobject string = oh_allocate_local_handle(); ManagedObject* lang_string = string_create_from_utf8(str->bytes, str->len); if (!lang_string) { // if OutOfMemory return NULL; } + string->object = lang_string; assert(!hythread_is_suspend_enabled()); + Global_Env* env = VM_Global_State::loader_env; + jvalue args[1]; + args[0].l = string; + assert(env->VM_intern); + vm_execute_java_method_array((jmethodID)env->VM_intern, + (jvalue*)&string, args); + assert(!exn_raised()); + assert(string); + assert(string->object); + // Atomically update the string structure since some other thread might be trying to make the same update. // The GC won't be able to enumerate here since GC is disabled, so there are no race conditions with GC. if (VM_Global_State::loader_env->compress_references) { COMPRESSED_REFERENCE compressed_lang_string = - (COMPRESSED_REFERENCE)((POINTER_SIZE_INT)lang_string - - (POINTER_SIZE_INT)VM_Global_State::loader_env->heap_base); + compress_reference(string->object); assert(is_compressed_reference(compressed_lang_string)); uint32 result = apr_atomic_cas32( /*destination*/ (volatile uint32 *)&str->intern.compressed_ref, @@ -345,7 +353,7 @@ ManagedObject * String_Pool::intern(Stri // Note the successful write of the object. gc_heap_write_global_slot_compressed( (COMPRESSED_REFERENCE *)&str->intern.compressed_ref, - (Managed_Object_Handle)lang_string); + (Managed_Object_Handle)string->object); // add this string to interned strings register_interned_string(str); } @@ -355,18 +363,19 @@ ManagedObject * String_Pool::intern(Stri void *result = (void *)apr_atomic_casptr( /*destination*/ (volatile void **)&str->intern.raw_ref, - /*exchange*/ (void *)lang_string, + /*exchange*/ (void *)string->object, /*comparand*/ (void *)NULL); if (result == NULL) { // Note the successful write of the object. gc_heap_write_global_slot( (Managed_Object_Handle *)&str->intern.raw_ref, - (Managed_Object_Handle)lang_string); + (Managed_Object_Handle)string->object); // add this string to interned strings register_interned_string(str); } // Some other thread may have beaten us to the slot. lang_string = str->intern.raw_ref; } + oh_discard_local_handle(string); return lang_string; } diff --git a/vm/vmcore/src/init/vm_init.cpp b/vm/vmcore/src/init/vm_init.cpp index e667f1c..4095d2c 100644 --- a/vm/vmcore/src/init/vm_init.cpp +++ b/vm/vmcore/src/init/vm_init.cpp @@ -369,6 +369,10 @@ static jint preload_classes(Global_Env * (class_lookup_field_recursive(vm_env->JavaLangString_Class, "bvalue", "[B") != NULL); vm_env->JavaLangString_VTable = vm_env->JavaLangString_Class->get_vtable(); + Class* VM_class = preload_class(vm_env, "org/apache/harmony/kernel/vm/VM"); + vm_env->VM_intern = class_lookup_method_recursive(VM_class, "intern", + "(Ljava/lang/String;)Ljava/lang/String;"); + TRACE2("init", "preloading exceptions"); vm_env->java_lang_Throwable_Class = preload_class(vm_env, vm_env->JavaLangThrowable_String); diff --git a/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/InternMap.java b/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/InternMap.java new file mode 100644 index 0000000..bb1ac9a --- /dev/null +++ b/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/InternMap.java @@ -0,0 +1,155 @@ +/* Copyright 1998, 2005 The Apache Software Foundation or its licensors, as applicable + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.kernel.vm; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * Implements weak hash map specialized for storing interned string pool. + * @see java.util.WeakHashMap + * @see WeakReference + */ +public class InternMap { + + private final ReferenceQueue referenceQueue; + + int elementCount; + + Entry[] elementData; + + private final int loadFactor; + + private int threshold; + + //Simple utility method to isolate unchecked cast for array creation + private static Entry[] newEntryArray(int size) { + return new Entry[size]; + } + + private static final class Entry/* extends WeakReference*/ { + int hash; + Entry next; + String key; + Entry(String key, ReferenceQueue queue) { + //super(key, queue); + this.key = key; + hash = key.hashCode(); + } + } + + public InternMap(int capacity) { + if (capacity >= 0) { + elementCount = 0; + elementData = newEntryArray(capacity == 0 ? 1 : capacity); + loadFactor = 7500; // Default load factor of 0.75 + computeMaxSize(); + referenceQueue = new ReferenceQueue(); + } else { + throw new IllegalArgumentException(); + } + } + + private void computeMaxSize() { + threshold = (int) ((long) elementData.length * loadFactor / 10000); + } + + void poll() + { + /* + Entry toRemove; + while ((toRemove = (Entry)referenceQueue.poll()) != null) { + removeEntry(toRemove); + } + */ + } + + void removeEntry(Entry toRemove) + { + Entry entry, last = null; + int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length; + entry = elementData[index]; + // Ignore queued entries which cannot be found, the user could + // have removed them before they were queued, i.e. using clear() + while (entry != null) { + if (toRemove == entry) { + if (last == null) { + elementData[index] = entry.next; + } else { + last.next = entry.next; + } + elementCount--; + break; + } + last = entry; + entry = entry.next; + } + } + + public String intern(String key) + { + int index = 0; + Entry entry; + String interned = null; + if (key == null) + return null; + int hash = key.hashCode(); + int length = elementData.length; + index = (hash & 0x7FFFFFFF) % length; + entry = elementData[index]; + while (entry != null && !key.equals(interned = (String)entry.key/*get()*/)) { + entry = entry.next; + } + + // if we found the entry, return it + if (entry != null) { + return interned; + } + + // no interned string found, put a new entry for it + if (++elementCount > threshold) { + rehash(); + index = (key.hashCode() & 0x7FFFFFFF) % elementData.length; + } + entry = new Entry(key, referenceQueue); + entry.next = elementData[index]; + elementData[index] = entry; + return key; + } + + private void rehash() + { + poll(); + int length = elementData.length << 1; + if (length == 0) { + length = 1; + } + Entry[] newData = newEntryArray(length); + for (int i = 0; i < elementData.length; i++) { + Entry entry = elementData[i]; + while (entry != null) { + int index = (entry.hash & 0x7FFFFFFF) % length; + Entry next = entry.next; + entry.next = newData[index]; + newData[index] = entry; + entry = next; + } + } + elementData = newData; + computeMaxSize(); + } +} + diff --git a/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/VM.java b/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/VM.java index d13294d..49e34a8 100644 --- a/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/VM.java +++ b/vm/vmcore/src/kernel_classes/javasrc/org/apache/harmony/kernel/vm/VM.java @@ -22,6 +22,8 @@ package org.apache.harmony.kernel.vm; import org.apache.harmony.vm.VMStack; +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; public final class VM { @@ -104,13 +106,21 @@ public final class VM { * @param s string to be interned * @return String that has the same contents as * argument, but from internal pool - */ - public static String intern(String s) { - return intern0(s); + */ + public static synchronized String intern(String s) + { + return internedStrings.intern(s); } /** * Invokes native string interning service. */ private static native String intern0(String s); + + private static InternMap internedStrings; + + static { + // initialize the storage for interned strings + internedStrings = new InternMap(32768); + } } diff --git a/vm/vmcore/src/kernel_classes/native/org_apache_harmony_kernel_vm_VM.cpp b/vm/vmcore/src/kernel_classes/native/org_apache_harmony_kernel_vm_VM.cpp index 8768a14..7a29582 100644 --- a/vm/vmcore/src/kernel_classes/native/org_apache_harmony_kernel_vm_VM.cpp +++ b/vm/vmcore/src/kernel_classes/native/org_apache_harmony_kernel_vm_VM.cpp @@ -38,10 +38,3 @@ (JNIEnv *jenv, jclass, jclass clazz) // reuse similar method in VMClassRegistry return Java_java_lang_VMClassRegistry_getClassLoader0(jenv, NULL, clazz); } - -JNIEXPORT jstring JNICALL -Java_org_apache_harmony_kernel_vm_VM_intern0(JNIEnv *jenv, jclass, jstring str) -{ - // call corresponding VM internal function - return string_intern(jenv, str); -} diff --git a/vm/vmcore/src/util/vm_strings.cpp b/vm/vmcore/src/util/vm_strings.cpp index 42a49b3..6179048 100644 --- a/vm/vmcore/src/util/vm_strings.cpp +++ b/vm/vmcore/src/util/vm_strings.cpp @@ -565,19 +565,6 @@ Java_java_lang_String *vm_instantiate_cp // Interning of strings -VMEXPORT jstring string_intern(JNIEnv *jenv, jobject jstr_obj) -{ - ASSERT_RAISE_AREA; - jboolean is_copy; - const char* val = jenv->GetStringUTFChars(jstr_obj, &is_copy); - String* str = VM_Global_State::loader_env->string_pool.lookup(val); - if (is_copy == JNI_TRUE) { - jenv->ReleaseStringUTFChars(jstr_obj, val); - } - - return String_to_interned_jstring(str); -} - jstring String_to_interned_jstring(String* str) { ASSERT_RAISE_AREA; -- 1.4.1