diff --git a/.gitignore b/.gitignore index c2acc737a..bf94681b7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,10 @@ obj/ bin/ .svn/ -**/.vscode/ \ No newline at end of file +**/.vscode/ + +.project +.DS_Store +.settings + +build-artifacts/ \ No newline at end of file diff --git a/runtime/src/main/java/com/tns/AppConfig.java b/runtime/src/main/java/com/tns/AppConfig.java index 3dad082f6..3c342fac7 100644 --- a/runtime/src/main/java/com/tns/AppConfig.java +++ b/runtime/src/main/java/com/tns/AppConfig.java @@ -1,14 +1,12 @@ package com.tns; import java.io.File; - import org.json.JSONObject; - import android.os.Build; import android.util.Log; class AppConfig { - private enum KnownKeys { + protected enum KnownKeys { V8FlagsKey("v8Flags", "--expose_gc"), CodeCacheKey("codeCache", false), HeapSnapshotScriptKey("heapSnapshotScript", ""), @@ -17,19 +15,8 @@ private enum KnownKeys { GcThrottleTime("gcThrottleTime", 0), MemoryCheckInterval("memoryCheckInterval", 0), FreeMemoryRatio("freeMemoryRatio", 0.0), - Profiling("profiling", ""); - - public static final KnownKeys[] asArray = { - V8FlagsKey, - CodeCacheKey, - HeapSnapshotScriptKey, - SnapshotFile, - ProfilerOutputDirKey, - GcThrottleTime, - MemoryCheckInterval, - FreeMemoryRatio, - Profiling - }; + Profiling("profiling", ""), + MarkingMode("markingMode", com.tns.MarkingMode.full); private final String name; private final Object defaultValue; @@ -42,19 +29,9 @@ private enum KnownKeys { public String getName() { return name; } - public Object getDefaultValue() { return defaultValue; } - - int getIndex() { - for (int i=0; i ref = weakInstances.get(javaObjectID); + if (ref == null) { + return false; + } else { + instance = ref.get(); + if (instance == null) { + // The Java was moved from strong to weak, and then the Java instance was collected. + weakInstances.remove(javaObjectID); + weakJavaObjectToID.remove(Integer.valueOf(javaObjectID)); + return false; + } else { + return true; + } + } + } else { + strongInstances.delete(javaObjectID); + strongJavaObjectToID.remove(instance); + + weakJavaObjectToID.put(instance, Integer.valueOf(javaObjectID)); + weakInstances.put(javaObjectID, new WeakReference(instance)); + + return true; + } + } + @RuntimeCallable private void checkWeakObjectAreAlive(ByteBuffer input, ByteBuffer output, int length) { input.position(0); diff --git a/runtime/src/main/jni/ObjectManager.cpp b/runtime/src/main/jni/ObjectManager.cpp index 96a0a19ad..774917104 100644 --- a/runtime/src/main/jni/ObjectManager.cpp +++ b/runtime/src/main/jni/ObjectManager.cpp @@ -17,8 +17,13 @@ using namespace v8; using namespace std; using namespace tns; -ObjectManager::ObjectManager(jobject javaRuntimeObject) - : m_javaRuntimeObject(javaRuntimeObject), m_env(JEnv()), m_numberOfGC(0), m_currentObjectId(0), m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) { +ObjectManager::ObjectManager(jobject javaRuntimeObject) : + m_javaRuntimeObject(javaRuntimeObject), + m_env(JEnv()), + m_numberOfGC(0), + m_currentObjectId(0), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) { + auto runtimeClass = m_env.FindClass("com/tns/Runtime"); assert(runtimeClass != nullptr); @@ -31,6 +36,9 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject) MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V"); assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); + MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z"); + assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr); + CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr); @@ -45,6 +53,10 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject) auto useGlobalRefs = m_env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID); m_useGlobalRefs = useGlobalRefs == JNI_TRUE; + + auto getMarkingModeOrdinalMethodID = m_env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", "()I"); + jint markingMode = m_env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID); + m_markingMode = static_cast(markingMode); } void ObjectManager::SetInstanceIsolate(Isolate* isolate) { @@ -57,8 +69,10 @@ void ObjectManager::Init(Isolate* isolate) { auto jsWrapperFunc = jsWrapperFuncTemplate->GetFunction(); m_poJsWrapperFunc = new Persistent(isolate, jsWrapperFunc); - isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll); - isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll); + if (m_markingMode != JavaScriptMarkingMode::None) { + isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll); + isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll); + } } @@ -80,32 +94,35 @@ JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local& object) ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfo(const Local& object) { JSInstanceInfo* jsInstanceInfo = nullptr; + if (IsJsRuntimeObject(object)) { + return GetJSInstanceInfoFromRuntimeObject(object); + } + return nullptr; +} - auto isolate = m_isolate; - HandleScope handleScope(isolate); +ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local& object) { + HandleScope handleScope(m_isolate); - if (IsJsRuntimeObject(object)) { - const int jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); - auto jsInfo = object->GetInternalField(jsInfoIdx); - if (jsInfo->IsUndefined()) { - //Typescript object layout has an object instance as child of the actual registered instance. checking for that - auto prototypeObject = object->GetPrototype().As(); - - if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) { - DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash()); - if (IsJsRuntimeObject(prototypeObject)) { - jsInfo = prototypeObject->GetInternalField(jsInfoIdx); - } + const int jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + auto jsInfo = object->GetInternalField(jsInfoIdx); + if (jsInfo->IsUndefined()) { + //Typescript object layout has an object instance as child of the actual registered instance. checking for that + auto prototypeObject = object->GetPrototype().As(); + + if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) { + DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash()); + if (IsJsRuntimeObject(prototypeObject)) { + jsInfo = prototypeObject->GetInternalField(jsInfoIdx); } } + } - if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) { - auto external = jsInfo.As(); - jsInstanceInfo = static_cast(external->Value()); - } + if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) { + auto external = jsInfo.As(); + return static_cast(external->Value()); } - return jsInstanceInfo; + return nullptr; } bool ObjectManager::IsJsRuntimeObject(const v8::Local& object) { @@ -211,7 +228,11 @@ void ObjectManager::Link(const Local& object, uint32_t javaObjectID, jcl auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle); // subscribe for JS GC event - objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer); + if (m_markingMode == JavaScriptMarkingMode::None) { + objectHandle->SetWeak(state, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer); + } else { + objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer); + } auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); @@ -263,6 +284,40 @@ void ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfoJSObjectWeakCallback(isolate, callbackState); } +void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo& data) { + ObjectWeakCallbackState* callbackState = data.GetParameter(); + + ObjectManager* thisPtr = callbackState->thisPtr; + + auto isolate = data.GetIsolate(); + + thisPtr->JSObjectFinalizer(isolate, callbackState); +} + +void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* callbackState) { + HandleScope handleScope(m_isolate); + Persistent* po = callbackState->target; + auto jsInstanceInfo = GetJSInstanceInfoFromRuntimeObject(po->Get(m_isolate)); + + if (jsInstanceInfo == nullptr) { + po->Reset(); + return; + } + + auto javaObjectID = jsInstanceInfo->JavaObjectID; + jboolean isJavaInstanceAlive = m_env.CallBooleanMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, javaObjectID); + if (isJavaInstanceAlive) { + // If the Java instance is alive, keep the JavaScript instance alive. + po->SetWeak(callbackState, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer); + } else { + // If the Java instance is dead, this JavaScript instance can be let die. + delete jsInstanceInfo; + auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); + po->Get(m_isolate)->SetInternalField(jsInfoIdx, Undefined(m_isolate)); + po->Reset(); + } +} + /* * When JS GC happens change state of the java counterpart to mirror state of JS object and REVIVE the JS object unconditionally * "Regular" js objects are pushed into the "regular objects" array diff --git a/runtime/src/main/jni/ObjectManager.h b/runtime/src/main/jni/ObjectManager.h index b9b0f3d00..41787a073 100644 --- a/runtime/src/main/jni/ObjectManager.h +++ b/runtime/src/main/jni/ObjectManager.h @@ -57,6 +57,21 @@ class ObjectManager { END }; + /** + * Memory management modes. Keep the members in sync with the java/com/tns/MarkingMode. + */ + enum JavaScriptMarkingMode { + /** + * For JavaScript instances with implementation objects that were marked for collection, + * MarkReachableObjects will scan the whole graph of reachable objects and keep strong reference to + * the Java instances of implementation objects. + */ + Full, + /** + * Fully suppress the MarkReachableObjects. + */ + None + }; private: @@ -128,6 +143,8 @@ class ObjectManager { JSInstanceInfo* GetJSInstanceInfo(const v8::Local& object); + JSInstanceInfo* GetJSInstanceInfoFromRuntimeObject(const v8::Local& object); + void ReleaseJSInstance(v8::Persistent* po, JSInstanceInfo* jsInstanceInfo); void ReleaseRegularObjects(); @@ -142,8 +159,12 @@ class ObjectManager { static void JSObjectWeakCallbackStatic(const v8::WeakCallbackInfo& data); + static void JSObjectFinalizerStatic(const v8::WeakCallbackInfo& data); + void JSObjectWeakCallback(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState); + void JSObjectFinalizer(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState); + bool HasImplObject(v8::Isolate* isolate, const v8::Local& obj); void MarkReachableObjects(v8::Isolate* isolate, const v8::Local& obj); @@ -196,6 +217,8 @@ class ObjectManager { bool m_useGlobalRefs; + JavaScriptMarkingMode m_markingMode; + jclass JAVA_LANG_CLASS; jmethodID GET_NAME_METHOD_ID; @@ -206,6 +229,8 @@ class ObjectManager { jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; + jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID; + jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID; v8::Persistent* m_poJsWrapperFunc; diff --git a/test-app/app/src/main/assets/app/package.json b/test-app/app/src/main/assets/app/package.json index 37d761fcb..a9e6c65c3 100644 --- a/test-app/app/src/main/assets/app/package.json +++ b/test-app/app/src/main/assets/app/package.json @@ -5,6 +5,7 @@ "profilerOutputDir": "", "gcThrottleTime": 500, "memoryCheckInterval": 10, - "freeMemoryRatio": 0.50 + "freeMemoryRatio": 0.50, + "markingMode": "full" } } \ No newline at end of file