Skip to content

Commit 7092883

Browse files
committed
Add "markingMode": "none" to the android key in the app/package.json to switch the memory management to one, that will not traverse the JS heap graph
1 parent 2e0f581 commit 7092883

File tree

6 files changed

+153
-9
lines changed

6 files changed

+153
-9
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ obj/
1515
bin/
1616
.svn/
1717

18-
**/.vscode/
18+
**/.vscode/
19+
20+
.project
21+
.DS_Store
22+
.settings

runtime/src/main/java/com/tns/AppConfig.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ private enum KnownKeys {
1717
GcThrottleTime("gcThrottleTime", 0),
1818
MemoryCheckInterval("memoryCheckInterval", 0),
1919
FreeMemoryRatio("freeMemoryRatio", 0.0),
20-
Profiling("profiling", "");
20+
Profiling("profiling", ""),
21+
MarkingMode("markingMode", "Full");
2122

2223
public static final KnownKeys[] asArray = {
2324
V8FlagsKey,
@@ -28,7 +29,8 @@ private enum KnownKeys {
2829
GcThrottleTime,
2930
MemoryCheckInterval,
3031
FreeMemoryRatio,
31-
Profiling
32+
Profiling,
33+
MarkingMode
3234
};
3335

3436
private final String name;
@@ -111,6 +113,9 @@ public AppConfig(File appDir) {
111113
if (androidObject.has(KnownKeys.FreeMemoryRatio.getName())) {
112114
values[KnownKeys.FreeMemoryRatio.getIndex()] = androidObject.getDouble(KnownKeys.FreeMemoryRatio.getName());
113115
}
116+
if (androidObject.has(KnownKeys.MarkingMode.getName())) {
117+
values[KnownKeys.MarkingMode.getIndex()] = androidObject.getString(KnownKeys.MarkingMode.getName());
118+
}
114119
}
115120
}
116121
} catch (Exception e) {
@@ -146,4 +151,5 @@ public double getFreeMemoryRatio() {
146151
public String getProfilingMode() {
147152
return (String)values[KnownKeys.Profiling.getIndex()];
148153
}
154+
public String getMarkingMode() { return (String)values[KnownKeys.MarkingMode.getIndex()]; }
149155
}

runtime/src/main/java/com/tns/Runtime.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ public DynamicConfiguration getDynamicConfig() {
202202
return dynamicConfig;
203203
}
204204

205+
public int getMarkingMode() {
206+
String mode = staticConfiguration == null || staticConfiguration.appConfig == null ? "full" : staticConfiguration.appConfig.getMarkingMode();
207+
if ("none".equals(mode)) {
208+
return 1;
209+
}
210+
return 0;
211+
}
212+
205213
public static boolean isInitialized() {
206214
Runtime runtime = Runtime.getCurrentRuntime();
207215

@@ -806,6 +814,38 @@ private void makeInstanceWeak(ByteBuffer buff, int length, boolean keepAsWeak) {
806814
}
807815
}
808816

817+
@RuntimeCallable
818+
private boolean makeInstanceWeakAndCheckIfAlive(int javaObjectID) {
819+
if (logger.isEnabled()) {
820+
logger.write("makeInstanceWeakAndCheckIfAlive instance " + javaObjectID);
821+
}
822+
Object instance = strongInstances.get(javaObjectID);
823+
if (instance == null) {
824+
WeakReference<Object> ref = weakInstances.get(javaObjectID);
825+
if (ref == null) {
826+
return false;
827+
} else {
828+
instance = ref.get();
829+
if (instance == null) {
830+
// The Java was moved from strong to weak, and then the Java instance was collected.
831+
weakInstances.remove(javaObjectID);
832+
weakJavaObjectToID.remove(Integer.valueOf(javaObjectID));
833+
return false;
834+
} else {
835+
return true;
836+
}
837+
}
838+
} else {
839+
strongInstances.delete(javaObjectID);
840+
strongJavaObjectToID.remove(instance);
841+
842+
weakJavaObjectToID.put(instance, Integer.valueOf(javaObjectID));
843+
weakInstances.put(javaObjectID, new WeakReference<Object>(instance));
844+
845+
return true;
846+
}
847+
}
848+
809849
@RuntimeCallable
810850
private void checkWeakObjectAreAlive(ByteBuffer input, ByteBuffer output, int length) {
811851
input.position(0);

runtime/src/main/jni/ObjectManager.cpp

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ using namespace v8;
1717
using namespace std;
1818
using namespace tns;
1919

20-
ObjectManager::ObjectManager(jobject javaRuntimeObject)
21-
: m_javaRuntimeObject(javaRuntimeObject), m_env(JEnv()), m_numberOfGC(0), m_currentObjectId(0), m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) {
20+
ObjectManager::ObjectManager(jobject javaRuntimeObject) :
21+
m_javaRuntimeObject(javaRuntimeObject),
22+
m_env(JEnv()),
23+
m_numberOfGC(0),
24+
m_currentObjectId(0),
25+
m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this),
26+
m_markingMode(JavaScriptMarkingMode::Full) {
27+
2228
auto runtimeClass = m_env.FindClass("com/tns/Runtime");
2329
assert(runtimeClass != nullptr);
2430

@@ -31,6 +37,9 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject)
3137
MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V");
3238
assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr);
3339

40+
MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z");
41+
assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr);
42+
3443
CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V");
3544
assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr);
3645

@@ -45,6 +54,14 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject)
4554

4655
auto useGlobalRefs = m_env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID);
4756
m_useGlobalRefs = useGlobalRefs == JNI_TRUE;
57+
58+
auto getMarkingModeMethodID = m_env.GetMethodID(runtimeClass, "getMarkingMode", "()I");
59+
jint markingMode = m_env.CallIntMethod(m_javaRuntimeObject, getMarkingModeMethodID);
60+
switch(markingMode) {
61+
case 1:
62+
m_markingMode = JavaScriptMarkingMode::None;
63+
break;
64+
}
4865
}
4966

5067
void ObjectManager::SetInstanceIsolate(Isolate* isolate) {
@@ -57,8 +74,10 @@ void ObjectManager::Init(Isolate* isolate) {
5774
auto jsWrapperFunc = jsWrapperFuncTemplate->GetFunction();
5875
m_poJsWrapperFunc = new Persistent<Function>(isolate, jsWrapperFunc);
5976

60-
isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll);
61-
isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll);
77+
if (m_markingMode != JavaScriptMarkingMode::None) {
78+
isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll);
79+
isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll);
80+
}
6281
}
6382

6483

@@ -211,7 +230,11 @@ void ObjectManager::Link(const Local<Object>& object, uint32_t javaObjectID, jcl
211230
auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle);
212231

213232
// subscribe for JS GC event
214-
objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer);
233+
if (m_markingMode == JavaScriptMarkingMode::None) {
234+
objectHandle->SetWeak(state, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer);
235+
} else {
236+
objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer);
237+
}
215238

216239
auto jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);
217240

@@ -263,6 +286,56 @@ void ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfo<ObjectWeak
263286
thisPtr->JSObjectWeakCallback(isolate, callbackState);
264287
}
265288

289+
void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo<ObjectWeakCallbackState>& data) {
290+
ObjectWeakCallbackState* callbackState = data.GetParameter();
291+
292+
ObjectManager* thisPtr = callbackState->thisPtr;
293+
294+
auto isolate = data.GetIsolate();
295+
296+
thisPtr->JSObjectFinalizer(isolate, callbackState);
297+
}
298+
299+
void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* callbackState) {
300+
Persistent<Object>* po = callbackState->target;
301+
302+
auto jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);
303+
auto jsInstance = po->Get(m_isolate);
304+
auto jsInfo = jsInstance->GetInternalField(jsInfoIdx);
305+
if (jsInfo->IsUndefined()) {
306+
// Typescript object layout has an object instance as child of the actual registered instance. checking for that
307+
auto prototypeObject = jsInstance->GetPrototype().As<Object>();
308+
if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) {
309+
DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash());
310+
if (IsJsRuntimeObject(prototypeObject)) {
311+
jsInfo = prototypeObject->GetInternalField(jsInfoIdx);
312+
}
313+
}
314+
}
315+
316+
if (jsInfo.IsEmpty() || !jsInfo->IsExternal()) {
317+
// The JavaScript instance has been forcefully disconnected from the Java instance.
318+
po->Reset();
319+
return;
320+
}
321+
322+
auto external = jsInfo.As<External>();
323+
auto jsInstanceInfo = static_cast<JSInstanceInfo *>(external->Value());
324+
auto javaObjectID = jsInstanceInfo->JavaObjectID;
325+
326+
jboolean isJavaInstanceAlive = m_env.CallBooleanMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, javaObjectID);
327+
if (isJavaInstanceAlive) {
328+
// If the Java instance is alive, keep the JavaScript instance alive.
329+
// TODO: Check should we really register the finalizer again?
330+
po->SetWeak(callbackState, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer);
331+
} else {
332+
// If the Java instance is dead, this JavaScript instance can be let die.
333+
delete jsInstanceInfo;
334+
jsInstance->SetInternalField(jsInfoIdx, Undefined(m_isolate));
335+
po->Reset();
336+
}
337+
}
338+
266339
/*
267340
* When JS GC happens change state of the java counterpart to mirror state of JS object and REVIVE the JS object unconditionally
268341
* "Regular" js objects are pushed into the "regular objects" array

runtime/src/main/jni/ObjectManager.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ class ObjectManager {
5757
END
5858
};
5959

60+
enum class JavaScriptMarkingMode {
61+
/**
62+
* For JavaScript instances with implementation objects that were marked for collection,
63+
* MarkReachableObjects will scann the whole graph of reachable objects and keep strong reference to
64+
* the Java instances of implementation objects.
65+
*/
66+
Full,
67+
/**
68+
* Fully suppress the MarkReachableObjects.
69+
*/
70+
None
71+
};
6072

6173
private:
6274

@@ -142,8 +154,12 @@ class ObjectManager {
142154

143155
static void JSObjectWeakCallbackStatic(const v8::WeakCallbackInfo<ObjectWeakCallbackState>& data);
144156

157+
static void JSObjectFinalizerStatic(const v8::WeakCallbackInfo<ObjectWeakCallbackState>& data);
158+
145159
void JSObjectWeakCallback(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState);
146160

161+
void JSObjectFinalizer(v8::Isolate* isolate, ObjectWeakCallbackState* callbackState);
162+
147163
bool HasImplObject(v8::Isolate* isolate, const v8::Local<v8::Object>& obj);
148164

149165
void MarkReachableObjects(v8::Isolate* isolate, const v8::Local<v8::Object>& obj);
@@ -196,6 +212,8 @@ class ObjectManager {
196212

197213
bool m_useGlobalRefs;
198214

215+
JavaScriptMarkingMode m_markingMode;
216+
199217
jclass JAVA_LANG_CLASS;
200218

201219
jmethodID GET_NAME_METHOD_ID;
@@ -206,6 +224,8 @@ class ObjectManager {
206224

207225
jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID;
208226

227+
jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID;
228+
209229
jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID;
210230

211231
v8::Persistent<v8::Function>* m_poJsWrapperFunc;

test-app/app/src/main/assets/app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"profilerOutputDir": "",
66
"gcThrottleTime": 500,
77
"memoryCheckInterval": 10,
8-
"freeMemoryRatio": 0.50
8+
"freeMemoryRatio": 0.50,
9+
"markingMode": "full"
910
}
1011
}

0 commit comments

Comments
 (0)