From 2645124d0f94e08b49b3a4a00c0937fcb448366a Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 15:26:37 -0300 Subject: [PATCH 1/5] test: benchmark the object lifecycle phases and many live wrappers Splits a wrapped object's cost into allocation (kept alive), release through a V8 GC and the Java GC that follows, for JS-constructed, JS-extended and Java-returned objects; and times an object return with 50k live wrappers. --- .../app/tests/benchmarks/jniBenchmarks.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js b/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js index d0fbb5725..4f4ff6c58 100644 --- a/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js +++ b/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js @@ -53,6 +53,41 @@ function measureCallback(name, cb, kind, iterations) { return report(name, samples); } + +// Splits an object's lifecycle into allocation (objects kept alive), release through a +// V8 GC (wrapper finalization and the Java-side strong -> weak bookkeeping), and the +// Java GC that follows. Each phase is reported per object. +function measureLifecycle(name, count, create) { + if (FILTER && name.indexOf(FILTER) < 0) return null; + count *= SCALE; + var javaRuntime = java.lang.Runtime.getRuntime(); + var alloc = [], release = [], javaGc = []; + var warm = []; + for (var w = 0; w < Math.max(1000, count >> 2); w++) warm.push(create()); + warm = null; + gc(); + javaRuntime.gc(); + for (var r = 0; r < ROUNDS; r++) { + var kept = new Array(count); + var start = performance.now(); + for (var i = 0; i < count; i++) kept[i] = create(); + alloc.push((performance.now() - start) * 1e6 / count); + + kept = null; + start = performance.now(); + gc(); + release.push((performance.now() - start) * 1e6 / count); + + start = performance.now(); + javaRuntime.gc(); + javaGc.push((performance.now() - start) * 1e6 / count); + } + report(name + " [alloc, kept alive]", alloc); + report(name + " [release: V8 gc]", release); + return report(name + " [release: Java gc]", javaGc); +} + + function buildBenchmarks() { var bench = new JniBench(); var other = bench.getOther(); @@ -244,6 +279,34 @@ function buildBenchmarks() { return s; }); }, + function () { + return measureLifecycle("lifecycle new java.lang.Object()", 20000, function () { + return new java.lang.Object(); + }); + }, + function () { + return measureLifecycle("lifecycle new Object.extend({})()", 20000, function () { + return new Extended(); + }); + }, + function () { + return measureLifecycle("lifecycle JniBench.newObjectInJava()", 20000, function () { + return JniBench.newObjectInJava(); + }); + }, + function () { + if (FILTER && "instance.self() [50k live wrappers]".indexOf(FILTER) < 0) return null; + var live = new Array(50000); + for (var i = 0; i < live.length; i++) live[i] = new java.lang.Object(); + var result = measureJs("instance.self() [50k live wrappers]", 50000, function (n) { + var s; + for (var i = 0; i < n; i++) s = bench.self(); + return s; + }); + globalThis.__jniBenchLive = live; + globalThis.__jniBenchLive = null; + return result; + }, function () { return measureCallback("callback.noArgs()", callback, 0, 20000); }, From a01579a777a793b1124205ef3d09ec0d0e635f0e Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 15:29:32 -0300 Subject: [PATCH 2/5] perf: trim per-construction work in RegisterInstance - EncodeSignature formatted the argument count through a std::stringstream on every constructor and dynamic method resolution; it now appends std::to_string into a reserved buffer. - The instance's class is the global ref ResolveClass already returned from the class cache, so the second FindClass(fullClassName) lookup is gone. - JsArgConverter skips parsing the signature of a zero-argument constructor. - makeInstanceStrong skips storeClass, which takes two locks, when the previous registration stored the same class. --- .../runtime/src/main/cpp/CallbackHandlers.cpp | 4 ++-- .../runtime/src/main/cpp/JsArgConverter.cpp | 6 ++++-- test-app/runtime/src/main/cpp/MethodCache.cpp | 18 ++++++------------ .../runtime/src/main/java/com/tns/Runtime.java | 9 ++++++++- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp index 0e82bc58b..803a0805c 100644 --- a/test-app/runtime/src/main/cpp/CallbackHandlers.cpp +++ b/test-app/runtime/src/main/cpp/CallbackHandlers.cpp @@ -154,8 +154,8 @@ bool CallbackHandlers::RegisterInstance(Isolate *isolate, const Local &j success = !localInstance.IsNull(); if (success) { - jclass instanceClass = env.FindClass(fullClassName); - objectManager->SetJavaClass(jsObject, instanceClass); + // ResolveClass returned the class cache's global ref for fullClassName. + objectManager->SetJavaClass(jsObject, generatedJavaClass); } else { DEBUG_WRITE_FORCE("RegisterInstance failed with null new instance class: %s", fullClassName.c_str()); diff --git a/test-app/runtime/src/main/cpp/JsArgConverter.cpp b/test-app/runtime/src/main/cpp/JsArgConverter.cpp index 3f563cbf2..bd4cafbca 100644 --- a/test-app/runtime/src/main/cpp/JsArgConverter.cpp +++ b/test-app/runtime/src/main/cpp/JsArgConverter.cpp @@ -85,8 +85,10 @@ JsArgConverter::JsArgConverter(const v8::FunctionCallbackInfo &args, m_error(Error()) { m_argsLen = args.Length(); - JniSignatureParser parser(methodSignature); - m_ownedTokens = parser.Parse(); + if (m_argsLen > 0) { + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + } for (int i = 0; i < m_argsLen; i++) { m_isValid = ConvertArg(args[i], i); diff --git a/test-app/runtime/src/main/cpp/MethodCache.cpp b/test-app/runtime/src/main/cpp/MethodCache.cpp index 076e18c6f..823e27160 100644 --- a/test-app/runtime/src/main/cpp/MethodCache.cpp +++ b/test-app/runtime/src/main/cpp/MethodCache.cpp @@ -13,7 +13,6 @@ #include "NativeScriptException.h" #include "Runtime.h" #include "ObjectManager.h" -#include using namespace v8; @@ -111,19 +110,14 @@ MethodCache::CacheMethodInfo MethodCache::ResolveConstructorSignature(const Args // Encoded signature .S/I....<...> string MethodCache::EncodeSignature(const string& className, const string& methodName, const FunctionCallbackInfo& args, bool isStatic) { - string sig(className); - sig.append("."); - if (isStatic) { - sig.append("S."); - } else { - sig.append("I."); - } + int len = args.Length(); + string sig; + sig.reserve(className.size() + methodName.size() + 8 + len * 12); + sig.append(className); + sig.append(isStatic ? ".S." : ".I."); sig.append(methodName); sig.append("."); - int len = args.Length(); - stringstream s; - s << len; - sig.append(s.str()); + sig.append(std::to_string(len)); for (int i = 0; i < len; i++) { sig.append("."); diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index 5bcf17000..6397160d0 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -1029,6 +1029,8 @@ private static String getTypeMetadata(Class clazz) { return ret; } + private Class lastStoredClass; + @RuntimeCallable private void makeInstanceStrong(Object instance, int objectId) { if (instance == null) { @@ -1040,7 +1042,12 @@ private void makeInstanceStrong(Object instance, int objectId) { strongJavaObjectToID.put(instance, key); Class clazz = instance.getClass(); - classStorageService.storeClass(clazz.getName(), clazz); + // Instances of one class are usually registered back to back, and storeClass takes + // two locks. A racing read only costs a redundant store. + if (clazz != lastStoredClass) { + classStorageService.storeClass(clazz.getName(), clazz); + lastStoredClass = clazz; + } if (logger != null && logger.isEnabled()) { logger.write("MakeInstanceStrong (" + key + ", " + instance.getClass().toString() + ")"); From 6ceea84e5a939034d830bcf84238a8b24e544035 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 15:36:30 -0300 Subject: [PATCH 3/5] refactor: remove the markingMode "full" machinery Only markingMode "none" is supported. "full" was already non-functional: its weak callback queued wrappers for processing that no longer existed, so they were revived forever. - ObjectManager always arms the finalizer path; the full-mode weak callback, its GC bookkeeping, the batch DirectBuffer plumbing and the unused declarations behind it are gone, as are DirectBuffer itself and the Java makeInstanceWeak(ByteBuffer)/checkWeakObjectAreAlive/getMarkingModeOrdinal. - AppConfig drops the key (native config indices shift accordingly) and logs a warning when an app still sets markingMode to anything but "none". - The __markingMode global is gone; testGC.js no longer gates on it, so its specs now run (they pass under the finalizer path). --- test-app/app/src/main/assets/app/package.json | 1 - .../app/src/main/assets/app/tests/testGC.js | 5 +- test-app/runtime/CMakeLists.txt | 1 - .../runtime/src/main/cpp/DirectBuffer.cpp | 56 ----- test-app/runtime/src/main/cpp/DirectBuffer.h | 30 --- .../runtime/src/main/cpp/ObjectManager.cpp | 194 +----------------- test-app/runtime/src/main/cpp/ObjectManager.h | 110 +--------- test-app/runtime/src/main/cpp/Runtime.cpp | 7 +- .../src/main/java/com/tns/AppConfig.java | 18 +- .../src/main/java/com/tns/MarkingMode.java | 9 - .../src/main/java/com/tns/Runtime.java | 61 ------ 11 files changed, 11 insertions(+), 481 deletions(-) delete mode 100644 test-app/runtime/src/main/cpp/DirectBuffer.cpp delete mode 100644 test-app/runtime/src/main/cpp/DirectBuffer.h delete mode 100644 test-app/runtime/src/main/java/com/tns/MarkingMode.java diff --git a/test-app/app/src/main/assets/app/package.json b/test-app/app/src/main/assets/app/package.json index 10bc180ec..2ca148662 100644 --- a/test-app/app/src/main/assets/app/package.json +++ b/test-app/app/src/main/assets/app/package.json @@ -6,7 +6,6 @@ "gcThrottleTime": 500, "memoryCheckInterval": 10, "freeMemoryRatio": 0.50, - "markingMode": "none", "maxLogcatObjectSize": 1024, "forceLog": false, "suppressCallJSMethodExceptions": false, diff --git a/test-app/app/src/main/assets/app/tests/testGC.js b/test-app/app/src/main/assets/app/tests/testGC.js index 89e337625..a4b63da59 100644 --- a/test-app/app/src/main/assets/app/tests/testGC.js +++ b/test-app/app/src/main/assets/app/tests/testGC.js @@ -1,7 +1,4 @@ -// Run GC tests only in Full marking mode -let describeFunc = __markingMode == 0 ? describe : xdescribe; - -describeFunc("Tests garbage collection", function () { +describe("Tests garbage collection", function () { var myCustomEquality = function(first, second) { return first == second; }; diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index 7ed82b473..eb2ca29ac 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -182,7 +182,6 @@ add_library( src/main/cpp/ConcurrentQueue.cpp src/main/cpp/Constants.cpp src/main/cpp/CrashBreadcrumbs.cpp - src/main/cpp/DirectBuffer.cpp src/main/cpp/ErrorEvents.cpp src/main/cpp/EventLoop.cpp src/main/cpp/Events.cpp diff --git a/test-app/runtime/src/main/cpp/DirectBuffer.cpp b/test-app/runtime/src/main/cpp/DirectBuffer.cpp deleted file mode 100644 index 1b3e6f8a3..000000000 --- a/test-app/runtime/src/main/cpp/DirectBuffer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "DirectBuffer.h" -#include "JniLocalRef.h" - -using namespace tns; - -DirectBuffer::DirectBuffer(uint32_t length) { - m_length = length; - - m_data = new int[m_length]; - - m_end = m_data + m_length; - - Reset(); - - int capacity = m_length * sizeof(int); - - JEnv env; - JniLocalRef buff(env.NewDirectByteBuffer(m_data, capacity)); - - m_buff = env.NewGlobalRef(buff); -} - -DirectBuffer::operator jobject() const { - return m_buff; -} - -int* DirectBuffer::GetData() const { - return m_data; -} - -int DirectBuffer::Length() const { - return m_length; -} - -int DirectBuffer::Size() const { - return m_pos - m_data; -} - -void DirectBuffer::Reset() { - m_pos = m_data; -} - -bool DirectBuffer::Write(int value) { - bool canWrite = m_pos < m_end; - if (canWrite) { - int bigEndianInt = __builtin_bswap32(value); - *(m_pos++) = bigEndianInt; - } - return canWrite; -} - -DirectBuffer::~DirectBuffer() { - JEnv env; - env.DeleteGlobalRef(m_buff); - delete[] m_data; -} diff --git a/test-app/runtime/src/main/cpp/DirectBuffer.h b/test-app/runtime/src/main/cpp/DirectBuffer.h deleted file mode 100644 index 4de8a8a63..000000000 --- a/test-app/runtime/src/main/cpp/DirectBuffer.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef DIRECTBUFFER_H_ -#define DIRECTBUFFER_H_ - -#include "JEnv.h" - -namespace tns { -class DirectBuffer { - public: - DirectBuffer(uint32_t capacity = 65536); - ~DirectBuffer(); - - operator jobject() const; - - int* GetData() const; - int Length() const; - int Size() const; - - void Reset(); - bool Write(int value); - - private: - jobject m_buff; - int* m_data; - jlong m_length; - int* m_pos; - int* m_end; -}; -} - -#endif /* DIRECTBUFFER_H_ */ diff --git a/test-app/runtime/src/main/cpp/ObjectManager.cpp b/test-app/runtime/src/main/cpp/ObjectManager.cpp index 87a73caf0..b3411d7eb 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.cpp +++ b/test-app/runtime/src/main/cpp/ObjectManager.cpp @@ -20,7 +20,6 @@ using namespace tns; ObjectManager::ObjectManager(jobject javaRuntimeObject) : m_javaRuntimeObject(javaRuntimeObject), - m_numberOfGC(0), m_currentObjectId(0), m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, ValidateWeakGlobalRefCallback, 1000, this) { @@ -37,12 +36,6 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject) auto useGlobalRefs = env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID); m_useGlobalRefs = useGlobalRefs == JNI_TRUE; - - auto getMarkingModeOrdinalMethodID = - env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", "()I"); - jint markingMode = - env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID); - m_markingMode = static_cast(markingMode); } void ObjectManager::InitializeJNI() { @@ -60,10 +53,6 @@ void ObjectManager::InitializeJNI() { runtimeClass, "getOrCreateJavaObjectID", "(Ljava/lang/Object;)I"); NS_CHECK(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); - MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID( - runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V"); - NS_CHECK(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); - MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z"); NS_CHECK(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr); @@ -72,11 +61,6 @@ void ObjectManager::InitializeJNI() { env.GetMethodID(runtimeClass, "releaseNativeCounterpart", "(I)V"); NS_CHECK(RELEASE_NATIVE_INSTANCE_METHOD_ID != nullptr); - CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = - env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", - "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V"); - NS_CHECK(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr); - JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); NS_CHECK(JAVA_LANG_CLASS != nullptr); @@ -328,19 +312,14 @@ void ObjectManager::Link(const Local& object, uint32_t javaObjectID, object->GetIdentityHash(), javaObjectID); auto jsInstanceInfo = - new JSInstanceInfo(false /*isJavaObjWeak*/, javaObjectID, clazz); + new JSInstanceInfo(javaObjectID, clazz); auto objectHandle = new Persistent(isolate, object); auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle); // subscribe for JS GC event - if (m_markingMode == JavaScriptMarkingMode::None) { - objectHandle->SetWeak(state, JSObjectFinalizerStatic, - WeakCallbackType::kFinalizer); - } else { - objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, - WeakCallbackType::kFinalizer); - } + objectHandle->SetWeak(state, JSObjectFinalizerStatic, + WeakCallbackType::kFinalizer); auto jsInfoIdx = static_cast(MetadataNodeKeys::JsInfo); @@ -390,17 +369,6 @@ string ObjectManager::GetClassName(jclass clazz) { return className; } -void ObjectManager::JSObjectWeakCallbackStatic( - const WeakCallbackInfo& data) { - ObjectWeakCallbackState* callbackState = data.GetParameter(); - - ObjectManager* thisPtr = callbackState->thisPtr; - - auto isolate = data.GetIsolate(); - - thisPtr->JSObjectWeakCallback(isolate, callbackState); -} - void ObjectManager::JSObjectFinalizerStatic( const WeakCallbackInfo& data) { ObjectWeakCallbackState* callbackState = data.GetParameter(); @@ -449,68 +417,6 @@ void ObjectManager::JSObjectFinalizer(Isolate* isolate, } } -/* - * 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 "Callback" js objects (ones that have - * implementation object) are pushed into two "special objects" array: -ones - * called for the first time which are originally strong in java -ones called - * for the second or next time which are already weak in java too These objects - * are categorized by "regular" and "callback" and saved in different arrays for - * performance optimizations during GC - * */ -void ObjectManager::JSObjectWeakCallback( - Isolate* isolate, ObjectWeakCallbackState* callbackState) { - HandleScope handleScope(isolate); - - Persistent* po = callbackState->target; - - auto itFound = m_visitedPOs.find(po); - - if (itFound == m_visitedPOs.end()) { - m_visitedPOs.insert(po); - - auto obj = Local::New(isolate, *po); - JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); - - if (jsInstanceInfo != nullptr) { - int javaObjectID = jsInstanceInfo->JavaObjectID; - - bool hasImplObj = HasImplObject(isolate, obj); - - DEBUG_WRITE("JSObjectWeakCallback objectId: %d, hasImplObj=%d", - javaObjectID, hasImplObj); - - if (hasImplObj) { - if (jsInstanceInfo->IsJavaObjectWeak) { - m_implObjWeak.emplace_back(po, javaObjectID); - } else { - m_implObjStrong.emplace(javaObjectID, po); - jsInstanceInfo->IsJavaObjectWeak = true; - } - } else { - if (m_markedForGC.empty()) { - // Emulates the behavior in the OnGcStarted callback. Đ¢his is - // necessary as the hooking to the V8 GC is done only on the - // markSweepCompact phase. The JSObjectWeakCallback however is still - // triggered in other V8 GC phases (scavenger for example). This - // creates a problem that there is no 'top' on the m_markedForGC - // stack. - GarbageCollectionInfo gcInfo(++m_numberOfGC); - gcInfo.markedForGC.push_back(po); - m_markedForGC.push(gcInfo); - } else { - auto& topGCInfo = m_markedForGC.top(); - topGCInfo.markedForGC.push_back(po); - } - } - } - } - - po->SetWeak(callbackState, JSObjectWeakCallbackStatic, - WeakCallbackType::kFinalizer); -} - int ObjectManager::GenerateNewObjectID() { const int one = 1; int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); @@ -562,94 +468,6 @@ ObjectManager::~ObjectManager() { m_cache.clear(); } -void ObjectManager::ReleaseJSInstance(Persistent* po, - JSInstanceInfo* jsInstanceInfo) { - int javaObjectID = jsInstanceInfo->JavaObjectID; - - auto it = m_idToObject.find(javaObjectID); - - if (it == m_idToObject.end()) { - stringstream ss; - ss << "(InternalError): Js object with id: " << javaObjectID - << " not found"; - throw NativeScriptException(ss.str()); - } - - NS_DCHECK(po == it->second->target); - - ObjectWeakCallbackState* callbackState = it->second; - m_idToObject.erase(it); - delete callbackState; - m_released.insert(po, javaObjectID); - po->Reset(); - - delete po; - delete jsInstanceInfo; - - DEBUG_WRITE("ReleaseJSObject instance disposed. id:%d", javaObjectID); -} - -/* - * The "regular" JS objects added on ObjectManager::JSObjectWeakCallback are - * dealt with(released) here. - * */ -void ObjectManager::ReleaseRegularObjects() { - TNSPERF(); - - HandleScope handleScope(m_isolate); - - auto propName = - String::NewFromUtf8(m_isolate, "t::gcNum", NewStringType::kNormal) - .ToLocalChecked(); - - auto& topGCInfo = m_markedForGC.top(); - auto& marked = topGCInfo.markedForGC; - int numberOfGC = topGCInfo.numberOfGC; - - for (auto po : marked) { - if (m_released.contains(po)) { - continue; - } - - auto obj = Local::New(m_isolate, *po); - - NS_DCHECK(!obj.IsEmpty()); - - Local gcNum; - V8GetPrivateValue(m_isolate, obj, propName, gcNum); - - bool isReachableFromImplementationObject = false; - - if (!gcNum.IsEmpty() && gcNum->IsNumber()) { - double objGcNum = gcNum.As()->Value(); - - // done so we can release only java objects from this GC stack and pass - // all objects that will be released in parent GC stacks - isReachableFromImplementationObject = objGcNum >= numberOfGC; - } - - JSInstanceInfo* jsInstanceInfo = GetJSInstanceInfo(obj); - - if (!isReachableFromImplementationObject) { - if (!jsInstanceInfo->IsJavaObjectWeak) { - jsInstanceInfo->IsJavaObjectWeak = true; - - ReleaseJSInstance(po, jsInstanceInfo); - } - } - } - - marked.clear(); -} - -bool ObjectManager::HasImplObject(Isolate* isolate, const Local& obj) { - auto implObject = MetadataNode::GetImplementationObject(isolate, obj); - - bool hasImplObj = !implObject.IsEmpty(); - - return hasImplObj; -} - jweak ObjectManager::NewWeakGlobalRefCallback(const int& javaObjectID, void* state) { auto objManager = reinterpret_cast(state); @@ -755,16 +573,10 @@ void ObjectManager::ReleaseNativeCounterpart(v8::Local& object) { } } -ObjectManager::JavaScriptMarkingMode ObjectManager::GetMarkingMode() { - return this->m_markingMode; -} - jclass ObjectManager::JAVA_LANG_CLASS = nullptr; jmethodID ObjectManager::GET_NAME_METHOD_ID = nullptr; jmethodID ObjectManager::GET_JAVAOBJECT_BY_ID_METHOD_ID = nullptr; jmethodID ObjectManager::GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = nullptr; -jmethodID ObjectManager::MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = nullptr; jmethodID ObjectManager::MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = nullptr; jmethodID ObjectManager::RELEASE_NATIVE_INSTANCE_METHOD_ID = nullptr; -jmethodID ObjectManager::CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = nullptr; diff --git a/test-app/runtime/src/main/cpp/ObjectManager.h b/test-app/runtime/src/main/cpp/ObjectManager.h index 5fbe4afce..e2a0e6b94 100644 --- a/test-app/runtime/src/main/cpp/ObjectManager.h +++ b/test-app/runtime/src/main/cpp/ObjectManager.h @@ -8,7 +8,6 @@ #include #include "ArgsWrapper.h" -#include "DirectBuffer.h" #include "JEnv.h" #include "JniLocalRef.h" #include "LRUCache.h" @@ -104,36 +103,13 @@ class ObjectManager { enum class MetadataNodeKeys { JsInfo, CallSuper, 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 - }; - - JavaScriptMarkingMode GetMarkingMode(); - private: static void InitializeJNI(); struct JSInstanceInfo { public: - JSInstanceInfo(bool isJavaObjectWeak, uint32_t javaObjectID, jclass claz) - : IsJavaObjectWeak(isJavaObjectWeak), - JavaObjectID(javaObjectID), - ObjectClazz(claz) {} + JSInstanceInfo(uint32_t javaObjectID, jclass claz) + : JavaObjectID(javaObjectID), ObjectClazz(claz) {} - bool IsJavaObjectWeak; uint32_t JavaObjectID; jclass ObjectClazz; }; @@ -154,81 +130,21 @@ class ObjectManager { uint32_t javaObjectID; }; - struct GarbageCollectionInfo { - GarbageCollectionInfo(int _numberOfGC) : numberOfGC(_numberOfGC) {} - std::vector*> markedForGC; - int numberOfGC; - }; - - class PersistentObjectIdSet { - public: - PersistentObjectIdSet() { /* TODO: use functors */ } - - void clear() { - m_POs.clear(); - m_IDs.clear(); - } - - void insert(v8::Persistent* po, int javaObjectId) { - m_POs.insert(po); - m_IDs.insert(javaObjectId); - } - - bool contains(v8::Persistent* po) { - return m_POs.find(po) != m_POs.end(); - } - - std::set*> m_POs; - std::set m_IDs; - }; - - struct PersistentObjectIdPair { - PersistentObjectIdPair(v8::Persistent* _po, int _javaObjectId) - : po(_po), javaObjectId(_javaObjectId) {} - v8::Persistent* po; - int javaObjectId; - }; - JSInstanceInfo* GetJSInstanceInfo(const v8::Local& object); JSInstanceInfo* GetJSInstanceInfoFromRuntimeObject( const v8::Local& object); - - void ReleaseJSInstance(v8::Persistent* po, - JSInstanceInfo* jsInstanceInfo); - - void ReleaseRegularObjects(); - - void MakeRegularObjectsWeak(const std::set& instances, - DirectBuffer& inputBuff); - - void MakeImplObjectsWeak( - const std::unordered_map*>& instances, - DirectBuffer& inputBuff); - - void CheckWeakObjectsAreAlive( - const std::vector& instances, - DirectBuffer& inputBuff, DirectBuffer& outputBuff); - v8::Local CreateJSWrapperHelper(jint javaObjectID, const std::string& typeName, jclass clazz); - 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); - jweak GetJavaObjectByID(uint32_t javaObjectID); jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); @@ -245,34 +161,16 @@ class ObjectManager { jobject m_javaRuntimeObject; - int m_numberOfGC; - v8::Isolate* m_isolate; - std::stack m_markedForGC; - std::unordered_map m_idToObject; - PersistentObjectIdSet m_released; - - std::set m_visited; - LRUCache m_cache; - std::set*> m_visitedPOs; - std::vector m_implObjWeak; - std::unordered_map*> m_implObjStrong; - volatile int m_currentObjectId; - DirectBuffer m_buff; - - DirectBuffer m_outBuff; - bool m_useGlobalRefs; - JavaScriptMarkingMode m_markingMode; - static jclass JAVA_LANG_CLASS; static jmethodID GET_NAME_METHOD_ID; @@ -281,14 +179,10 @@ class ObjectManager { static jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; - static jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; - static jmethodID MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID; static jmethodID RELEASE_NATIVE_INSTANCE_METHOD_ID; - static jmethodID CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID; - v8::Persistent* m_poJsWrapperFunc; }; } // namespace tns diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index db0173ba2..b9dd7a30e 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -270,14 +270,14 @@ void Runtime::Init(JNIEnv* env, jstring filesPath, jstring nativeLibDir, { JEnv jEnv(env); JniLocalRef discardUncaught(env->GetObjectArrayElement( - args, (jsize)11 /* KnownKeys.DiscardUncaughtJsExceptions */)); + args, (jsize)10 /* KnownKeys.DiscardUncaughtJsExceptions */)); if (!discardUncaught.IsNull()) { m_discardUncaughtJsExceptions = JType::BooleanValue(jEnv, discardUncaught) == JNI_TRUE; } JniLocalRef uncaughtErrorPolicy(env->GetObjectArrayElement( - args, (jsize)14 /* KnownKeys.UncaughtErrorPolicy */)); + args, (jsize)13 /* KnownKeys.UncaughtErrorPolicy */)); if (!uncaughtErrorPolicy.IsNull()) { auto policy = ArgConverter::jstringToString(uncaughtErrorPolicy); if (policy == "throw") { @@ -942,9 +942,6 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, ArgConverter::ConvertToV8String(isolate, "__releaseNativeCounterpart"), FunctionTemplate::New( isolate, CallbackHandlers::ReleaseNativeCounterpartCallback)); - globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__markingMode"), - Number::New(isolate, m_objectManager->GetMarkingMode()), - readOnlyFlags); globalTemplate->Set( ArgConverter::ConvertToV8String(isolate, "__runOnMainThread"), FunctionTemplate::New(isolate, diff --git a/test-app/runtime/src/main/java/com/tns/AppConfig.java b/test-app/runtime/src/main/java/com/tns/AppConfig.java index d56f32c0a..18cd74773 100644 --- a/test-app/runtime/src/main/java/com/tns/AppConfig.java +++ b/test-app/runtime/src/main/java/com/tns/AppConfig.java @@ -17,7 +17,6 @@ protected enum KnownKeys { MemoryCheckInterval("memoryCheckInterval", 0), FreeMemoryRatio("freeMemoryRatio", 0.0), Profiling("profiling", ""), - MarkingMode("markingMode", com.tns.MarkingMode.none), HandleTimeZoneChanges("handleTimeZoneChanges", false), MaxLogcatObjectSize("maxLogcatObjectSize", 1024), ForceLog("forceLog", false), @@ -125,17 +124,9 @@ public AppConfig(File appDir) { if (androidObject.has(KnownKeys.FreeMemoryRatio.getName())) { values[KnownKeys.FreeMemoryRatio.ordinal()] = androidObject.getDouble(KnownKeys.FreeMemoryRatio.getName()); } - if (androidObject.has(KnownKeys.MarkingMode.getName())) { - try { - String markingModeString = androidObject.getString(KnownKeys.MarkingMode.getName()); - MarkingMode markingMode = MarkingMode.valueOf(markingModeString); - values[KnownKeys.MarkingMode.ordinal()] = markingMode; - } catch (Exception e) { - Log.e("JS", String.format("Failed to parse marking mode: %s. The default %s will be used!", e.getMessage(), ((MarkingMode)KnownKeys.MarkingMode.getDefaultValue()).name())); - if (com.tns.Runtime.isDebuggable()) { - e.printStackTrace(); - } - } + String markingMode = androidObject.optString("markingMode", "none"); + if (!markingMode.equals("none")) { + Log.w("JS", String.format("markingMode \"%s\" is no longer supported; \"none\" is used.", markingMode)); } if (androidObject.has(KnownKeys.HandleTimeZoneChanges.getName())) { values[KnownKeys.HandleTimeZoneChanges.ordinal()] = androidObject.getBoolean(KnownKeys.HandleTimeZoneChanges.getName()); @@ -189,9 +180,6 @@ public String getProfilingMode() { return (String)values[KnownKeys.Profiling.ordinal()]; } - public MarkingMode getMarkingMode() { - return (MarkingMode)values[KnownKeys.MarkingMode.ordinal()]; - } public boolean handleTimeZoneChanges() { return (boolean)values[KnownKeys.HandleTimeZoneChanges.ordinal()]; diff --git a/test-app/runtime/src/main/java/com/tns/MarkingMode.java b/test-app/runtime/src/main/java/com/tns/MarkingMode.java deleted file mode 100644 index 224a87a91..000000000 --- a/test-app/runtime/src/main/java/com/tns/MarkingMode.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.tns; - -/** - * Created by cankov on 24/08/2017. - */ -enum MarkingMode { - full, - none -} diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index 6397160d0..7e0257c34 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -533,15 +533,6 @@ public static boolean getLineBreakpointsEnabled() { } } - @RuntimeCallable - public int getMarkingModeOrdinal() { - if (staticConfiguration != null && staticConfiguration.appConfig != null) { - return staticConfiguration.appConfig.getMarkingMode().ordinal(); - } else { - return ((MarkingMode) AppConfig.KnownKeys.MarkingMode.getDefaultValue()).ordinal(); - } - } - public static boolean isInitialized() { Runtime runtime = Runtime.getCurrentRuntime(); return (runtime != null) ? runtime.isInitializedImpl() : false; @@ -1054,30 +1045,6 @@ private void makeInstanceStrong(Object instance, int objectId) { } } - private void makeInstanceWeak(int javaObjectID, boolean keepAsWeak) { - if (logger.isEnabled()) { - logger.write("makeInstanceWeak instance " + javaObjectID + " keepAsWeak=" + keepAsWeak); - } - Object instance = strongInstances.get(javaObjectID); - - if (keepAsWeak) { - weakJavaObjectToID.put(instance, Integer.valueOf(javaObjectID)); - weakInstances.put(javaObjectID, new WeakReference(instance)); - } - - strongInstances.remove(javaObjectID); - strongJavaObjectToID.remove(instance); - } - - @RuntimeCallable - private void makeInstanceWeak(ByteBuffer buff, int length, boolean keepAsWeak) { - buff.position(0); - for (int i = 0; i < length; i++) { - int javaObjectId = buff.getInt(); - makeInstanceWeak(javaObjectId, keepAsWeak); - } - } - @RuntimeCallable private boolean makeInstanceWeakAndCheckIfAlive(int javaObjectID) { if (logger.isEnabled()) { @@ -1110,34 +1077,6 @@ private boolean makeInstanceWeakAndCheckIfAlive(int javaObjectID) { } } - @RuntimeCallable - private void checkWeakObjectAreAlive(ByteBuffer input, ByteBuffer output, int length) { - input.position(0); - output.position(0); - for (int i = 0; i < length; i++) { - int javaObjectId = input.getInt(); - - WeakReference weakRef = weakInstances.get(javaObjectId); - - int isReleased; - - if (weakRef != null) { - Object instance = weakRef.get(); - - if (instance == null) { - isReleased = 1; - weakInstances.remove(javaObjectId); - } else { - isReleased = 0; - } - } else { - isReleased = (strongInstances.get(javaObjectId) == null) ? 1 : 0; - } - - output.putInt(isReleased); - } - } - @RuntimeCallable private Object getJavaObjectByID(int javaObjectID) throws Exception { if (logger.isEnabled()) { From 76d59fc67421ea7c8144a7a11159f02f5462f175 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 16:55:50 -0300 Subject: [PATCH 4/5] refactor: pass the native startup options by name initNativeScript received AppConfig's whole value array and the native side read five entries by KnownKeys ordinal, so removing or reordering a key silently shifted them (and read past the end under CheckJNI). The five values are now explicit initNativeScript parameters, gathered into a Runtime::StartupOptions on the native side. --- test-app/runtime/src/main/cpp/Runtime.cpp | 48 +++++-------------- test-app/runtime/src/main/cpp/Runtime.h | 13 ++++- .../runtime/src/main/cpp/com_tns_Runtime.cpp | 10 +++- .../src/main/java/com/tns/AppConfig.java | 13 +++-- .../src/main/java/com/tns/Runtime.java | 7 ++- 5 files changed, 47 insertions(+), 44 deletions(-) diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index b9dd7a30e..33eabea5b 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -223,7 +223,7 @@ ObjectManager* Runtime::GetObjectManager() const { return m_objectManager; } void Runtime::Init(JNIEnv* _env, jobject obj, int runtimeId, jstring filesPath, jstring nativeLibDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, - jobjectArray args, jstring callingDir, + const StartupOptions& options, jstring callingDir, int maxLogcatObjectSize, bool forceLog) { JEnv env(_env); @@ -233,7 +233,7 @@ void Runtime::Init(JNIEnv* _env, jobject obj, int runtimeId, jstring filesPath, try { runtime->Init(env, filesPath, nativeLibDir, enableLog, isDebuggable, - packageName, args, callingDir, maxLogcatObjectSize, forceLog); + packageName, options, callingDir, maxLogcatObjectSize, forceLog); } catch (...) { // The Java side rolls back its own state and rethrows; without this the // native half of a failed bootstrap would stay allocated and registered. @@ -244,8 +244,8 @@ void Runtime::Init(JNIEnv* _env, jobject obj, int runtimeId, jstring filesPath, void Runtime::Init(JNIEnv* env, jstring filesPath, jstring nativeLibDir, bool verboseLoggingEnabled, bool isDebuggable, - jstring packageName, jobjectArray args, jstring callingDir, - int maxLogcatObjectSize, bool forceLog) { + jstring packageName, const StartupOptions& options, + jstring callingDir, int maxLogcatObjectSize, bool forceLog) { LogEnabled = verboseLoggingEnabled; auto filesRoot = ArgConverter::jstringToString(filesPath); @@ -259,40 +259,18 @@ void Runtime::Init(JNIEnv* env, jstring filesPath, jstring nativeLibDir, CrashBreadcrumbs::RegisterRuntime(m_id); Constants::APP_ROOT_FOLDER_PATH = filesRoot + "/app/"; - // read config options passed from Java - // Indices correspond to positions in the com.tns.AppConfig.KnownKeys enum - JniLocalRef v8Flags(env->GetObjectArrayElement(args, 0)); - Constants::V8_STARTUP_FLAGS = ArgConverter::jstringToString(v8Flags); - JniLocalRef cacheCode(env->GetObjectArrayElement(args, 1)); - Constants::V8_CACHE_COMPILED_CODE = (bool)cacheCode; - JniLocalRef profilerOutputDir(env->GetObjectArrayElement(args, 2)); - - { - JEnv jEnv(env); - JniLocalRef discardUncaught(env->GetObjectArrayElement( - args, (jsize)10 /* KnownKeys.DiscardUncaughtJsExceptions */)); - if (!discardUncaught.IsNull()) { - m_discardUncaughtJsExceptions = - JType::BooleanValue(jEnv, discardUncaught) == JNI_TRUE; - } - - JniLocalRef uncaughtErrorPolicy(env->GetObjectArrayElement( - args, (jsize)13 /* KnownKeys.UncaughtErrorPolicy */)); - if (!uncaughtErrorPolicy.IsNull()) { - auto policy = ArgConverter::jstringToString(uncaughtErrorPolicy); - if (policy == "throw") { - m_uncaughtErrorPolicy = UncaughtErrorPolicy::Throw; - } else { - // AppConfig validates and warns about unknown values; anything that - // is not "throw" behaves as the default. - m_uncaughtErrorPolicy = UncaughtErrorPolicy::Report; - } - } - } + Constants::V8_STARTUP_FLAGS = options.v8Flags; + Constants::V8_CACHE_COMPILED_CODE = options.codeCache; + m_discardUncaughtJsExceptions = options.discardUncaughtJsExceptions; + // AppConfig validates and warns about unknown values; anything that is not + // "throw" behaves as the default. + m_uncaughtErrorPolicy = options.uncaughtErrorPolicy == "throw" + ? UncaughtErrorPolicy::Throw + : UncaughtErrorPolicy::Report; DEBUG_WRITE("Initializing Telerik NativeScript"); - auto profilerOutputDirStr = ArgConverter::jstringToString(profilerOutputDir); + const auto& profilerOutputDirStr = options.profilerOutputDir; NativeScriptException::Init(); try { diff --git a/test-app/runtime/src/main/cpp/Runtime.h b/test-app/runtime/src/main/cpp/Runtime.h index 05e169578..4a427525f 100644 --- a/test-app/runtime/src/main/cpp/Runtime.h +++ b/test-app/runtime/src/main/cpp/Runtime.h @@ -133,12 +133,21 @@ class Runtime { static void Init(JavaVM* vm, void* reserved); - static void Init(JNIEnv* _env, jobject obj, int runtimeId, jstring filesPath, jstring nativeLibsDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, jobjectArray args, jstring callingDir, int maxLogcatObjectSize, + // The com.tns.AppConfig values initNativeScript passes by name. + struct StartupOptions { + std::string v8Flags; + bool codeCache; + std::string profilerOutputDir; + bool discardUncaughtJsExceptions; + std::string uncaughtErrorPolicy; + }; + + static void Init(JNIEnv* _env, jobject obj, int runtimeId, jstring filesPath, jstring nativeLibsDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, const StartupOptions& options, jstring callingDir, int maxLogcatObjectSize, bool forceLog); static void SetManualInstrumentationMode(jstring mode); - void Init(JNIEnv* env, jstring filesPath, jstring nativeLibsDir, bool verboseLoggingEnabled, bool isDebuggable, jstring packageName, jobjectArray args, jstring callingDir, int maxLogcatObjectSize, bool forceLog); + void Init(JNIEnv* env, jstring filesPath, jstring nativeLibsDir, bool verboseLoggingEnabled, bool isDebuggable, jstring packageName, const StartupOptions& options, jstring callingDir, int maxLogcatObjectSize, bool forceLog); v8::Isolate* GetIsolate() const; diff --git a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp index 6a85ee825..f4f0386c0 100644 --- a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp +++ b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp @@ -92,10 +92,16 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_setManualInstrumentationModeLegac setManualInstrumentationModeFast_impl(_env, clazz, mode); } -extern "C" JNIEXPORT void Java_com_tns_Runtime_initNativeScript(JNIEnv* _env, jobject obj, jint runtimeId, jstring filesPath, jstring nativeLibDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, jobjectArray args, jstring callingDir, jint maxLogcatObjectSize, jboolean forceLog) { +extern "C" JNIEXPORT void Java_com_tns_Runtime_initNativeScript(JNIEnv* _env, jobject obj, jint runtimeId, jstring filesPath, jstring nativeLibDir, jboolean verboseLoggingEnabled, jboolean isDebuggable, jstring packageName, jstring v8Flags, jboolean codeCache, jstring profilerOutputDir, jboolean discardUncaughtJsExceptions, jstring uncaughtErrorPolicy, jstring callingDir, jint maxLogcatObjectSize, jboolean forceLog) { JEnv::EntryScope envScope(_env); try { - Runtime::Init(_env, obj, runtimeId, filesPath, nativeLibDir, verboseLoggingEnabled, isDebuggable, packageName, args, callingDir, maxLogcatObjectSize, forceLog); + Runtime::StartupOptions options{ + ArgConverter::jstringToString(v8Flags), + codeCache == JNI_TRUE, + ArgConverter::jstringToString(profilerOutputDir), + discardUncaughtJsExceptions == JNI_TRUE, + ArgConverter::jstringToString(uncaughtErrorPolicy)}; + Runtime::Init(_env, obj, runtimeId, filesPath, nativeLibDir, verboseLoggingEnabled, isDebuggable, packageName, options, callingDir, maxLogcatObjectSize, forceLog); } catch (NativeScriptException& e) { e.ReThrowToJava(); } catch (std::exception e) { diff --git a/test-app/runtime/src/main/java/com/tns/AppConfig.java b/test-app/runtime/src/main/java/com/tns/AppConfig.java index 18cd74773..7d7dcd086 100644 --- a/test-app/runtime/src/main/java/com/tns/AppConfig.java +++ b/test-app/runtime/src/main/java/com/tns/AppConfig.java @@ -23,7 +23,6 @@ protected enum KnownKeys { DiscardUncaughtJsExceptions("discardUncaughtJsExceptions", false), EnableLineBreakpoins("enableLineBreakpoints", false), EnableMultithreadedJavascript("enableMultithreadedJavascript", false), - // Appended last: native code reads this array by ordinal. UncaughtErrorPolicy("uncaughtErrorPolicy", "report"); private final String name; @@ -152,8 +151,16 @@ public AppConfig(File appDir) { } } - public Object[] getAsArray() { - return values; + public String getV8Flags() { + return (String)values[KnownKeys.V8FlagsKey.ordinal()]; + } + + public boolean getCodeCache() { + return (boolean)values[KnownKeys.CodeCacheKey.ordinal()]; + } + + public String getProfilerOutputDir() { + return (String)values[KnownKeys.ProfilerOutputDirKey.ordinal()]; } private static Object[] makeDefaultOptions() { diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index 7e0257c34..c21c02b5f 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -36,7 +36,8 @@ public class Runtime { private native void initNativeScript(int runtimeId, String filesPath, String nativeLibDir, boolean verboseLoggingEnabled, boolean isDebuggable, String packageName, - Object[] v8Options, String callingDir, int maxLogcatObjectSize, boolean forceLog); + String v8Flags, boolean codeCache, String profilerOutputDir, boolean discardUncaughtJsExceptions, String uncaughtErrorPolicy, + String callingDir, int maxLogcatObjectSize, boolean forceLog); private native void runModule(int runtimeId, String filePath) throws NativeScriptException; @@ -702,7 +703,9 @@ private void init(Logger logger, String appName, String nativeLibDir, File rootD boolean forceConsoleLog = appConfig.getForceLog() || "timeline".equalsIgnoreCase(appConfig.getProfilingMode()); - initNativeScript(getRuntimeId(), Module.getApplicationFilesPath(), nativeLibDir, logger.isEnabled(), isDebuggable, appName, appConfig.getAsArray(), + initNativeScript(getRuntimeId(), Module.getApplicationFilesPath(), nativeLibDir, logger.isEnabled(), isDebuggable, appName, + appConfig.getV8Flags(), appConfig.getCodeCache(), appConfig.getProfilerOutputDir(), + appConfig.getDiscardUncaughtJsExceptions(), appConfig.getUncaughtErrorPolicy(), callingJsDir, appConfig.getMaxLogcatObjectSize(), forceConsoleLog); if (logger.isEnabled()) { From 16ac56f2af065538c06512ea67cbbf3286b17602 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 20:54:57 -0300 Subject: [PATCH 5/5] test: keep the 50k wrappers pinned while their benchmark is timed --- .../app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js b/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js index 4f4ff6c58..0e6c54fe8 100644 --- a/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js +++ b/test-app/app/src/main/assets/app/tests/benchmarks/jniBenchmarks.js @@ -298,12 +298,13 @@ function buildBenchmarks() { if (FILTER && "instance.self() [50k live wrappers]".indexOf(FILTER) < 0) return null; var live = new Array(50000); for (var i = 0; i < live.length; i++) live[i] = new java.lang.Object(); + // Pinned from a global: an optimized frame may drop a local after its last use. + globalThis.__jniBenchLive = live; var result = measureJs("instance.self() [50k live wrappers]", 50000, function (n) { var s; for (var i = 0; i < n; i++) s = bench.self(); return s; }); - globalThis.__jniBenchLive = live; globalThis.__jniBenchLive = null; return result; },