diff --git a/be/src/agent/task_worker_pool.cpp b/be/src/agent/task_worker_pool.cpp index c0660939a6fd6c..4e0a9756c37cc2 100644 --- a/be/src/agent/task_worker_pool.cpp +++ b/be/src/agent/task_worker_pool.cpp @@ -2399,7 +2399,7 @@ void clean_udf_cache_callback(const TAgentTaskRequest& req) { if (doris::config::enable_java_support) { LOG(INFO) << "clean udf cache start: " << req.clean_udf_cache_req.function_signature; static_cast( - JniUtil::clean_udf_class_load_cache(req.clean_udf_cache_req.function_signature)); + Jni::Util::clean_udf_class_load_cache(req.clean_udf_cache_req.function_signature)); LOG(INFO) << "clean udf cache finish: " << req.clean_udf_cache_req.function_signature; } } diff --git a/be/src/io/fs/benchmark/benchmark_factory.hpp b/be/src/io/fs/benchmark/benchmark_factory.hpp index df08aaeca91aaf..47bedaad3d7625 100644 --- a/be/src/io/fs/benchmark/benchmark_factory.hpp +++ b/be/src/io/fs/benchmark/benchmark_factory.hpp @@ -104,7 +104,7 @@ class MultiBenchmark { Status status = Status::OK(); if (doris::config::enable_java_support) { // Init jni - status = doris::JniUtil::Init(); + status = doris::Jni::Util::Init(); if (!status.ok()) { LOG(WARNING) << "Failed to initialize JNI: " << status; exit(1); diff --git a/be/src/io/fs/hdfs_file_writer.cpp b/be/src/io/fs/hdfs_file_writer.cpp index 9237c795af9422..5a47f19ffeb125 100644 --- a/be/src/io/fs/hdfs_file_writer.cpp +++ b/be/src/io/fs/hdfs_file_writer.cpp @@ -128,7 +128,7 @@ class HdfsWriteMemUsageRecorder { private: // clang-format off size_t max_jvm_heap_size() const { - return JniUtil::get_max_jni_heap_memory_size(); + return Jni::Util::get_max_jni_heap_memory_size(); } // clang-format on [[maybe_unused]] std::size_t cur_memory_comsuption {0}; diff --git a/be/src/service/doris_main.cpp b/be/src/service/doris_main.cpp index ead7426a56408d..8c8c14f92150e6 100644 --- a/be/src/service/doris_main.cpp +++ b/be/src/service/doris_main.cpp @@ -490,7 +490,7 @@ int main(int argc, char** argv) { Status status = Status::OK(); if (doris::config::enable_java_support) { // Init jni - status = doris::JniUtil::Init(); + status = doris::Jni::Util::Init(); if (!status.ok()) { LOG(WARNING) << "Failed to initialize JNI: " << status; exit(1); diff --git a/be/src/util/doris_metrics.cpp b/be/src/util/doris_metrics.cpp index b92c1380f43fa7..7a92b3676f142b 100644 --- a/be/src/util/doris_metrics.cpp +++ b/be/src/util/doris_metrics.cpp @@ -441,8 +441,8 @@ void DorisMetrics::initialize(bool init_system_metrics, const std::setExceptionCheck()); - jboolean is_copy; - const char* utf_chars = env->GetStringUTFChars(jstr, &is_copy); - bool exception_check = static_cast(env->ExceptionCheck()); - if (utf_chars == nullptr || exception_check) { - if (exception_check) { - env->ExceptionClear(); - } - if (utf_chars != nullptr) { - env->ReleaseStringUTFChars(jstr, utf_chars); - } - const auto* fail_message = "GetStringUTFChars failed. Probable OOM on JVM side"; - LOG(WARNING) << fail_message; - return Status::JniError(fail_message); +Status Env::GetJNIEnvSlowPath(JNIEnv** env) { + DCHECK(!tls_env_) << "Call GetJNIEnv() fast path"; + +#ifdef USE_LIBHDFS3 + std::call_once(g_vm_once, FindOrCreateJavaVM); + int rc = g_vm->GetEnv(reinterpret_cast(&tls_env_), JNI_VERSION_1_8); + if (rc == JNI_EDETACHED) { + rc = g_vm->AttachCurrentThread((void**)&tls_env_, nullptr); + } + if (rc != 0 || tls_env_ == nullptr) { + return Status::JniError("Unable to get JVM: {}", rc); } - out->env = env; - out->jstr = jstr; - out->utf_chars = utf_chars; +#else + // the hadoop libhdfs will do all the stuff + std::call_once(g_jvm_conf_once, SetEnvIfNecessary); + tls_env_ = getJNIEnv(); +#endif + *env = tls_env_; return Status::OK(); } -Status JniLocalFrame::push(JNIEnv* env, int max_local_ref) { - DCHECK(env_ == nullptr); - DCHECK_GT(max_local_ref, 0); - if (env->PushLocalFrame(max_local_ref) < 0) { +Status Env::GetJniExceptionMsg(JNIEnv* env, bool log_stack, const string& prefix) { + jthrowable exc = env->ExceptionOccurred(); + Defer def {[&]() { env->DeleteLocalRef(exc); }}; + if (exc == nullptr) { + return Status::OK(); + } + env->ExceptionClear(); + DCHECK(throwable_to_string_id_ != nullptr); + const char* oom_msg_template = + "$0 threw an unchecked exception. The JVM is likely out " + "of memory (OOM)."; + jstring msg = static_cast( + env->CallStaticObjectMethod(jni_util_cl_, throwable_to_string_id_, exc)); + if (env->ExceptionOccurred()) { env->ExceptionClear(); - return Status::JniError("failed to push frame"); + string oom_msg = absl::Substitute(oom_msg_template, "throwableToString"); + LOG(WARNING) << oom_msg; + return Status::JniError(oom_msg); } - env_ = env; - return Status::OK(); + + std::string return_msg; + auto* msg_str = env->GetStringUTFChars(msg, nullptr); + return_msg += msg_str; + env->ReleaseStringUTFChars((jstring)msg, msg_str); + + if (log_stack) { + jstring stack = static_cast( + env->CallStaticObjectMethod(jni_util_cl_, throwable_to_stack_trace_id_, exc)); + if (env->ExceptionOccurred()) { + env->ExceptionClear(); + string oom_msg = absl::Substitute(oom_msg_template, "throwableToStackTrace"); + LOG(WARNING) << oom_msg; + return Status::JniError(oom_msg); + } + + auto* stask_str = env->GetStringUTFChars(stack, nullptr); + LOG(WARNING) << stask_str; + env->ReleaseStringUTFChars(stack, stask_str); + } + + return Status::JniError("{}{}", prefix, return_msg); } -void JniUtil::parse_max_heap_memory_size_from_jvm(JNIEnv* env) { +bool Util::jvm_inited_ = false; + +jlong Util::max_jvm_heap_memory_size_ = 0; +GlobalObject Util::jni_scanner_loader_obj_; +MethodId Util::jni_scanner_loader_method_; +MethodId Util::_clean_udf_cache_method_id; +GlobalClass Util::hashmap_class; +MethodId Util::hashmap_constructor; +MethodId Util::hashmap_put; +GlobalClass Util::mapClass; +MethodId Util::mapEntrySetMethod; +GlobalClass Util::mapEntryClass; +MethodId Util::getEntryKeyMethod; +MethodId Util::getEntryValueMethod; +GlobalClass Util::setClass; +MethodId Util::iteratorSetMethod; +GlobalClass Util::iteratorClass; +MethodId Util::iteratorHasNextMethod; +MethodId Util::iteratorNextMethod; + +void Util::_parse_max_heap_memory_size_from_jvm() { // The start_be.sh would set JAVA_OPTS inside LIBHDFS_OPTS std::string java_opts = getenv("LIBHDFS_OPTS") ? getenv("LIBHDFS_OPTS") : ""; std::istringstream iss(java_opts); @@ -249,329 +286,108 @@ void JniUtil::parse_max_heap_memory_size_from_jvm(JNIEnv* env) { LOG(INFO) << "the max_jvm_heap_memory_size_ is " << max_jvm_heap_memory_size_; } -size_t JniUtil::get_max_jni_heap_memory_size() { +size_t Util::get_max_jni_heap_memory_size() { #if defined(USE_LIBHDFS3) || defined(BE_TEST) return std::numeric_limits::max(); #else - static std::once_flag parse_max_heap_memory_size_from_jvm_flag; - std::call_once(parse_max_heap_memory_size_from_jvm_flag, parse_max_heap_memory_size_from_jvm, - tls_env_); + static std::once_flag _parse_max_heap_memory_size_from_jvm_flag; + std::call_once(_parse_max_heap_memory_size_from_jvm_flag, _parse_max_heap_memory_size_from_jvm); return max_jvm_heap_memory_size_; #endif } -Status JniUtil::GetJNIEnvSlowPath(JNIEnv** env) { - DCHECK(!tls_env_) << "Call GetJNIEnv() fast path"; +Status Util::_init_jni_scanner_loader() { + JNIEnv* env = nullptr; + RETURN_IF_ERROR(Jni::Env::Get(&env)); + LocalClass jni_scanner_loader_cls; + std::string jni_scanner_loader_str = "org/apache/doris/common/classloader/ScannerLoader"; + RETURN_IF_ERROR(find_class(env, jni_scanner_loader_str.c_str(), &jni_scanner_loader_cls)); -#ifdef USE_LIBHDFS3 - std::call_once(g_vm_once, FindOrCreateJavaVM); - int rc = g_vm->GetEnv(reinterpret_cast(&tls_env_), JNI_VERSION_1_8); - if (rc == JNI_EDETACHED) { - rc = g_vm->AttachCurrentThread((void**)&tls_env_, nullptr); - } - if (rc != 0 || tls_env_ == nullptr) { - return Status::JniError("Unable to get JVM: {}", rc); - } -#else - // the hadoop libhdfs will do all the stuff - std::call_once(g_jvm_conf_once, SetEnvIfNecessary); - tls_env_ = getJNIEnv(); -#endif - *env = tls_env_; - return Status::OK(); -} + MethodId jni_scanner_loader_constructor; + RETURN_IF_ERROR(jni_scanner_loader_cls.get_method(env, "", "()V", + &jni_scanner_loader_constructor)); -Status JniUtil::GetJniExceptionMsg(JNIEnv* env, bool log_stack, const string& prefix) { - jthrowable exc = env->ExceptionOccurred(); - if (exc == nullptr) { - return Status::OK(); - } - env->ExceptionClear(); - DCHECK(throwable_to_string_id() != nullptr); - const char* oom_msg_template = - "$0 threw an unchecked exception. The JVM is likely out " - "of memory (OOM)."; - jstring msg = static_cast( - env->CallStaticObjectMethod(jni_util_class(), throwable_to_string_id(), exc)); - if (env->ExceptionOccurred()) { - env->ExceptionClear(); - string oom_msg = absl::Substitute(oom_msg_template, "throwableToString"); - LOG(WARNING) << oom_msg; - return Status::JniError(oom_msg); - } - JniUtfCharGuard msg_str_guard; - RETURN_IF_ERROR(JniUtfCharGuard::create(env, msg, &msg_str_guard)); - if (log_stack) { - jstring stack = static_cast( - env->CallStaticObjectMethod(jni_util_class(), throwable_to_stack_trace_id(), exc)); - if (env->ExceptionOccurred()) { - env->ExceptionClear(); - string oom_msg = absl::Substitute(oom_msg_template, "throwableToStackTrace"); - LOG(WARNING) << oom_msg; - return Status::JniError(oom_msg); - } - JniUtfCharGuard c_stack_guard; - RETURN_IF_ERROR(JniUtfCharGuard::create(env, stack, &c_stack_guard)); - LOG(WARNING) << c_stack_guard.get(); - } + RETURN_IF_ERROR(jni_scanner_loader_cls.get_method(env, "getLoadedClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + &jni_scanner_loader_method_)); - env->DeleteLocalRef(exc); - return Status::JniError("{}{}", prefix, msg_str_guard.get()); -} + MethodId load_jni_scanner; + RETURN_IF_ERROR( + jni_scanner_loader_cls.get_method(env, "loadAllScannerJars", "()V", &load_jni_scanner)); -Status JniUtil::convert_to_java_map(JNIEnv* env, const std::map& map, - jobject* hashmap_object) { - jclass hashmap_class = env->FindClass("java/util/HashMap"); - RETURN_ERROR_IF_EXC(env); - jmethodID hashmap_constructor = env->GetMethodID(hashmap_class, "", "(I)V"); - RETURN_ERROR_IF_EXC(env); - jobject hashmap_local_object = env->NewObject(hashmap_class, hashmap_constructor, map.size()); - RETURN_ERROR_IF_EXC(env); - jmethodID hashmap_put = env->GetMethodID( - hashmap_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - RETURN_ERROR_IF_EXC(env); - for (const auto& it : map) { - jstring key = env->NewStringUTF(it.first.c_str()); - jstring value = env->NewStringUTF(it.second.c_str()); - env->CallObjectMethod(hashmap_local_object, hashmap_put, key, value); - RETURN_ERROR_IF_EXC(env); - env->DeleteLocalRef(key); - env->DeleteLocalRef(value); - } - env->DeleteLocalRef(hashmap_class); - RETURN_IF_ERROR(LocalToGlobalRef(env, hashmap_local_object, hashmap_object)); - env->DeleteLocalRef(hashmap_local_object); - return Status::OK(); -} - -Status JniUtil::convert_to_cpp_map(JNIEnv* env, jobject map, - std::map* resultMap) { - // Get the class and method ID of the java.util.Map interface - jclass mapClass = env->FindClass("java/util/Map"); - RETURN_ERROR_IF_EXC(env); - jmethodID entrySetMethod = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;"); - - // Get the class and method ID of the java.util.Set interface - jclass setClass = env->FindClass("java/util/Set"); - RETURN_ERROR_IF_EXC(env); - jmethodID iteratorSetMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;"); - - // Get the class and method ID of the java.util.Iterator interface - jclass iteratorClass = env->FindClass("java/util/Iterator"); - RETURN_ERROR_IF_EXC(env); - jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z"); - jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;"); - - // Get the class and method ID of the java.util.Map.Entry interface - jclass entryClass = env->FindClass("java/util/Map$Entry"); - RETURN_ERROR_IF_EXC(env); - jmethodID getKeyMethod = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;"); - jmethodID getValueMethod = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;"); - - // Call the entrySet method to get the set of key-value pairs - jobject entrySet = env->CallObjectMethod(map, entrySetMethod); - RETURN_ERROR_IF_EXC(env); - - // Call the iterator method on the set to iterate over the key-value pairs - jobject iteratorSet = env->CallObjectMethod(entrySet, iteratorSetMethod); - RETURN_ERROR_IF_EXC(env); - - // Iterate over the key-value pairs - while (env->CallBooleanMethod(iteratorSet, hasNextMethod)) { - RETURN_ERROR_IF_EXC(env); - - // Get the current entry - jobject entry = env->CallObjectMethod(iteratorSet, nextMethod); - RETURN_ERROR_IF_EXC(env); - - // Get the key and value from the entry - jobject javaKey = env->CallObjectMethod(entry, getKeyMethod); - RETURN_ERROR_IF_EXC(env); - - jobject javaValue = env->CallObjectMethod(entry, getValueMethod); - RETURN_ERROR_IF_EXC(env); - - // Convert the key and value to C++ strings - const char* key = env->GetStringUTFChars(static_cast(javaKey), nullptr); - const char* value = env->GetStringUTFChars(static_cast(javaValue), nullptr); - - // Store the key-value pair in the map - (*resultMap)[key] = value; - - // Release the string references - env->ReleaseStringUTFChars(static_cast(javaKey), key); - env->ReleaseStringUTFChars(static_cast(javaValue), value); - - // Delete local references - env->DeleteLocalRef(entry); - env->DeleteLocalRef(javaKey); - env->DeleteLocalRef(javaValue); - } + RETURN_IF_ERROR(jni_scanner_loader_cls.get_method( + env, "cleanUdfClassLoader", "(Ljava/lang/String;)V", &_clean_udf_cache_method_id)); - // Delete local references - env->DeleteLocalRef(iteratorSet); - env->DeleteLocalRef(entrySet); - env->DeleteLocalRef(mapClass); - env->DeleteLocalRef(setClass); - env->DeleteLocalRef(iteratorClass); - env->DeleteLocalRef(entryClass); + RETURN_IF_ERROR(jni_scanner_loader_cls.new_object(env, jni_scanner_loader_constructor) + .call(&jni_scanner_loader_obj_)); + RETURN_IF_ERROR(jni_scanner_loader_obj_.call_void_method(env, load_jni_scanner).call()); return Status::OK(); } -Status JniUtil::GetGlobalClassRef(JNIEnv* env, const char* class_str, jclass* class_ref) { - *class_ref = nullptr; - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF(jclass, local_cl, env, FindClass(class_str)); - RETURN_IF_ERROR(LocalToGlobalRef(env, local_cl, reinterpret_cast(class_ref))); - return Status::OK(); -} +Status Util::clean_udf_class_load_cache(const std::string& function_signature) { + JNIEnv* env = nullptr; + RETURN_IF_ERROR(Jni::Env::Get(&env)); -Status JniUtil::LocalToGlobalRef(JNIEnv* env, jobject local_ref, jobject* global_ref) { - *global_ref = env->NewGlobalRef(local_ref); - // NewGlobalRef: - // Returns a global reference to the given obj. - // - //May return NULL if: - // obj refers to null - // the system has run out of memory - // obj was a weak global reference and has already been garbage collected - if (*global_ref == nullptr) { - return Status::JniError( - "LocalToGlobalRef fail,global ref is NULL,maybe the system has run out of memory."); - } + LocalString function_signature_jstr; + RETURN_IF_ERROR( + LocalString::new_string(env, function_signature.c_str(), &function_signature_jstr)); - //NewGlobalRef not throw exception,maybe we just need check NULL. - RETURN_ERROR_IF_EXC(env); - return Status::OK(); -} + RETURN_IF_ERROR(jni_scanner_loader_obj_.call_void_method(env, _clean_udf_cache_method_id) + .with_arg(function_signature_jstr) + .call()); -Status JniUtil::init_jni_scanner_loader(JNIEnv* env) { - // Get scanner loader; - jclass jni_scanner_loader_cls; - std::string jni_scanner_loader_str = "org/apache/doris/common/classloader/ScannerLoader"; - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, jni_scanner_loader_str.c_str(), - &jni_scanner_loader_cls)); - jmethodID jni_scanner_loader_constructor = - env->GetMethodID(jni_scanner_loader_cls, "", "()V"); - RETURN_ERROR_IF_EXC(env); - jni_scanner_loader_method_ = env->GetMethodID(jni_scanner_loader_cls, "getLoadedClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); - if (jni_scanner_loader_method_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find ScannerLoader.getLoadedClass method."); - } - RETURN_ERROR_IF_EXC(env); - jmethodID load_jni_scanner = - env->GetMethodID(jni_scanner_loader_cls, "loadAllScannerJars", "()V"); - RETURN_ERROR_IF_EXC(env); - - jobject jni_scanner_loader_local_obj = - env->NewObject(jni_scanner_loader_cls, jni_scanner_loader_constructor); - jni_scanner_loader_obj_ = env->NewGlobalRef(jni_scanner_loader_local_obj); - RETURN_ERROR_IF_EXC(env); - env->DeleteLocalRef(jni_scanner_loader_local_obj); - RETURN_ERROR_IF_EXC(env); - env->CallVoidMethod(jni_scanner_loader_obj_, load_jni_scanner); - RETURN_ERROR_IF_EXC(env); - - _clean_udf_cache_method_id = env->GetMethodID(jni_scanner_loader_cls, "cleanUdfClassLoader", - "(Ljava/lang/String;)V"); - if (_clean_udf_cache_method_id == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find removeUdfClassLoader method."); - } - RETURN_ERROR_IF_EXC(env); return Status::OK(); } -Status JniUtil::clean_udf_class_load_cache(const std::string& function_signature) { +Status Util::_init_collect_class() { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - jstring function_signature_jstr = env->NewStringUTF(function_signature.c_str()); - RETURN_ERROR_IF_EXC(env); - env->CallVoidMethod(jni_scanner_loader_obj_, _clean_udf_cache_method_id, - function_signature_jstr); - RETURN_ERROR_IF_EXC(env); - env->DeleteLocalRef(function_signature_jstr); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + // for hashmap + RETURN_IF_ERROR(find_class(env, "java/util/HashMap", &hashmap_class)); + RETURN_IF_ERROR(hashmap_class.get_method(env, "", "(I)V", &hashmap_constructor)); + RETURN_IF_ERROR(hashmap_class.get_method( + env, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", &hashmap_put)); + + //for map + RETURN_IF_ERROR(find_class(env, "java/util/Map", &mapClass)); + RETURN_IF_ERROR(mapClass.get_method(env, "entrySet", "()Ljava/util/Set;", &mapEntrySetMethod)); + + //for set + RETURN_IF_ERROR(find_class(env, "java/util/Set", &setClass)); + RETURN_IF_ERROR( + setClass.get_method(env, "iterator", "()Ljava/util/Iterator;", &iteratorSetMethod)); + + // for iterator + RETURN_IF_ERROR(find_class(env, "java/util/Iterator", &iteratorClass)); + RETURN_IF_ERROR(iteratorClass.get_method(env, "hasNext", "()Z", &iteratorHasNextMethod)); + RETURN_IF_ERROR( + iteratorClass.get_method(env, "next", "()Ljava/lang/Object;", &iteratorNextMethod)); + + //for map entry + RETURN_IF_ERROR(find_class(env, "java/util/Map$Entry", &mapEntryClass)); + RETURN_IF_ERROR( + mapEntryClass.get_method(env, "getKey", "()Ljava/lang/Object;", &getEntryKeyMethod)); + + RETURN_IF_ERROR(mapEntryClass.get_method(env, "getValue", "()Ljava/lang/Object;", + &getEntryValueMethod)); + return Status::OK(); } -Status JniUtil::get_jni_scanner_class(JNIEnv* env, const char* classname, - jclass* jni_scanner_class) { - // Get JNI scanner class by class name; - jstring class_name_str = env->NewStringUTF(classname); - RETURN_ERROR_IF_EXC(env); - - jobject loaded_class_obj = env->CallObjectMethod(jni_scanner_loader_obj_, - jni_scanner_loader_method_, class_name_str); - RETURN_ERROR_IF_EXC(env); - - *jni_scanner_class = reinterpret_cast(env->NewGlobalRef(loaded_class_obj)); - RETURN_ERROR_IF_EXC(env); - - env->DeleteLocalRef(loaded_class_obj); - RETURN_ERROR_IF_EXC(env); - env->DeleteLocalRef(class_name_str); - RETURN_ERROR_IF_EXC(env); +Status Util::Init() { + RETURN_IF_ERROR(Env::Init()); + RETURN_IF_ERROR(_init_register_natives()); + RETURN_IF_ERROR(_init_collect_class()); + RETURN_IF_ERROR(_init_jni_scanner_loader()); + jvm_inited_ = true; + DorisMetrics::instance()->init_jvm_metrics(); return Status::OK(); } -Status JniUtil::Init() { - // RETURN_IF_ERROR(LibJVMLoader::instance().load()); - - // Get the JNIEnv* corresponding to current thread. +Status Util::_init_register_natives() { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - - if (env == nullptr) { - return Status::JniError("Failed to get/create JVM"); - } - // Find JniUtil class and create a global ref. - jclass local_jni_util_cl = env->FindClass("org/apache/doris/common/jni/utils/JniUtil"); - if (local_jni_util_cl == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil class."); - } - jni_util_cl_ = reinterpret_cast(env->NewGlobalRef(local_jni_util_cl)); - if (jni_util_cl_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to create global reference to JniUtil class."); - } - env->DeleteLocalRef(local_jni_util_cl); - if (env->ExceptionOccurred()) { - return Status::JniError("Failed to delete local reference to JniUtil class."); - } - - // Find InternalException class and create a global ref. - jclass local_internal_exc_cl = - env->FindClass("org/apache/doris/common/exception/InternalException"); - if (local_internal_exc_cl == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil class."); - } - internal_exc_cl_ = reinterpret_cast(env->NewGlobalRef(local_internal_exc_cl)); - if (internal_exc_cl_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to create global reference to JniUtil class."); - } - env->DeleteLocalRef(local_internal_exc_cl); - if (env->ExceptionOccurred()) { - return Status::JniError("Failed to delete local reference to JniUtil class."); - } - + RETURN_IF_ERROR(Jni::Env::Get(&env)); // Find JNINativeMethod class and create a global ref. jclass local_jni_native_exc_cl = env->FindClass("org/apache/doris/common/jni/utils/JNINativeMethod"); @@ -581,18 +397,6 @@ Status JniUtil::Init() { } return Status::JniError("Failed to find JNINativeMethod class."); } - jni_native_method_exc_cl_ = - reinterpret_cast(env->NewGlobalRef(local_jni_native_exc_cl)); - if (jni_native_method_exc_cl_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to create global reference to JNINativeMethod class."); - } - env->DeleteLocalRef(local_jni_native_exc_cl); - if (env->ExceptionOccurred()) { - return Status::JniError("Failed to delete local reference to JNINativeMethod class."); - } static char memory_alloc_name[] = "memoryTrackerMalloc"; static char memory_alloc_sign[] = "(J)J"; @@ -611,57 +415,15 @@ Status JniUtil::Init() { (void*)&JavaNativeMethods::memoryFreeBatch}, }; - int res = env->RegisterNatives(jni_native_method_exc_cl_, java_native_methods, + int res = env->RegisterNatives(local_jni_native_exc_cl, java_native_methods, sizeof(java_native_methods) / sizeof(java_native_methods[0])); DCHECK_EQ(res, 0); - - // Throwable toString() - throwable_to_string_id_ = env->GetStaticMethodID(jni_util_cl_, "throwableToString", - "(Ljava/lang/Throwable;)Ljava/lang/String;"); - if (throwable_to_string_id_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil.throwableToString method."); + if (res) [[unlikely]] { + return Status::JniError("Failed to RegisterNatives."); } - - // throwableToStackTrace() - throwable_to_stack_trace_id_ = env->GetStaticMethodID( - jni_util_cl_, "throwableToStackTrace", "(Ljava/lang/Throwable;)Ljava/lang/String;"); - if (throwable_to_stack_trace_id_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil.throwableToFullStackTrace method."); - } - - get_jvm_metrics_id_ = env->GetStaticMethodID(jni_util_cl_, "getJvmMemoryMetrics", "()[B"); - if (get_jvm_metrics_id_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil.getJvmMemoryMetrics method."); - } - - get_jvm_threads_id_ = env->GetStaticMethodID(jni_util_cl_, "getJvmThreadsInfo", "([B)[B"); - if (get_jvm_threads_id_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil.getJvmThreadsInfo method."); - } - - get_jmx_json_ = env->GetStaticMethodID(jni_util_cl_, "getJMXJson", "()[B"); - if (get_jmx_json_ == nullptr) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::JniError("Failed to find JniUtil.getJMXJson method."); - } - RETURN_IF_ERROR(init_jni_scanner_loader(env)); - jvm_inited_ = true; - DorisMetrics::instance()->init_jvm_metrics(env); return Status::OK(); } + +} // namespace Jni #include "common/compile_check_end.h" } // namespace doris diff --git a/be/src/util/jni-util.h b/be/src/util/jni-util.h index 58dfb70ae4faa6..59a9cd503de022 100644 --- a/be/src/util/jni-util.h +++ b/be/src/util/jni-util.h @@ -36,34 +36,21 @@ extern "C" JNIEnv* getJNIEnv(void); #endif namespace doris { -class JniUtil; -#define RETURN_ERROR_IF_EXC(env) \ - do { \ - if (env->ExceptionCheck()) [[unlikely]] \ - return JniUtil::GetJniExceptionMsg(env); \ +#define RETURN_ERROR_IF_EXC(env) \ + do { \ + if (env->ExceptionCheck()) [[unlikely]] \ + return Jni::Env::GetJniExceptionMsg(env); \ } while (false) -#define JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF(type, result, env, func) \ - type result = env->func; \ - DEFER(env->DeleteLocalRef(result)); \ - RETURN_ERROR_IF_EXC(env) - -#define JNI_CALL_METHOD_CHECK_EXCEPTION(type, result, env, func) \ - type result = env->func; \ - RETURN_ERROR_IF_EXC(env) - //In order to reduce the potential risks caused by not handling exceptions, // you need to refer to https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html // to confirm whether the jni method will throw an exception. -class JniUtil { +namespace Jni { +class Env { public: - static Status Init() WARN_UNUSED_RESULT; - - static jmethodID throwable_to_string_id() { return throwable_to_string_id_; } - - static Status GetJNIEnv(JNIEnv** env) { + static Status Get(JNIEnv** env) { if (tls_env_) { *env = tls_env_; } else { @@ -72,138 +59,1138 @@ class JniUtil { return status; } } - if (*env == nullptr) { + if (*env == nullptr) [[unlikely]] { return Status::JniError("Failed to get JNIEnv: it is nullptr."); } return Status::OK(); } - //jclass is generally a local reference. - //Method ID and field ID values are forever. - //If you want to use the jclass across multiple threads or multiple calls into the JNI code you need - // to create a global reference to it with GetGlobalClassRef(). - static Status GetGlobalClassRef(JNIEnv* env, const char* class_str, - jclass* class_ref) WARN_UNUSED_RESULT; - - static Status LocalToGlobalRef(JNIEnv* env, jobject local_ref, - jobject* global_ref) WARN_UNUSED_RESULT; + static Status Init() { return init_throw_exception(); } static Status GetJniExceptionMsg(JNIEnv* env, bool log_stack = true, const std::string& prefix = "") WARN_UNUSED_RESULT; - static jclass jni_util_class() { return jni_util_cl_; } - static jmethodID throwable_to_stack_trace_id() { return throwable_to_stack_trace_id_; } +private: + static Status GetJNIEnvSlowPath(JNIEnv** env); + static Status init_throw_exception() { + JNIEnv* env = nullptr; + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + // Find JniUtil class and create a global ref. + jclass local_jni_util_cl = env->FindClass("org/apache/doris/common/jni/utils/JniUtil"); + if (local_jni_util_cl == nullptr) { + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + } + return Status::JniError("Failed to find JniUtil class."); + } + jni_util_cl_ = reinterpret_cast(env->NewGlobalRef(local_jni_util_cl)); + env->DeleteLocalRef(local_jni_util_cl); + if (jni_util_cl_ == nullptr) { + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + } + return Status::JniError("Failed to create global reference to JniUtil class."); + } + if (env->ExceptionOccurred()) { + return Status::JniError("Failed to delete local reference to JniUtil class."); + } + + // Throwable toString() + throwable_to_string_id_ = env->GetStaticMethodID( + jni_util_cl_, "throwableToString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + if (throwable_to_string_id_ == nullptr) { + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + } + return Status::JniError("Failed to find JniUtil.throwableToString method."); + } - static const int64_t INITIAL_RESERVED_BUFFER_SIZE = 1024; - // TODO: we need a heuristic strategy to increase buffer size for variable-size output. - static inline int64_t IncreaseReservedBufferSize(int n) { - return INITIAL_RESERVED_BUFFER_SIZE << n; + // throwableToStackTrace() + throwable_to_stack_trace_id_ = env->GetStaticMethodID( + jni_util_cl_, "throwableToStackTrace", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + if (throwable_to_stack_trace_id_ == nullptr) { + if (env->ExceptionOccurred()) { + env->ExceptionDescribe(); + } + return Status::JniError("Failed to find JniUtil.throwableToFullStackTrace method."); + } + return Status::OK(); } - static Status get_jni_scanner_class(JNIEnv* env, const char* classname, jclass* loaded_class); - static Status convert_to_java_map(JNIEnv* env, const std::map& map, - jobject* hashmap_object); - static Status convert_to_cpp_map(JNIEnv* env, jobject map, - std::map* resultMap); - static size_t get_max_jni_heap_memory_size(); - static Status clean_udf_class_load_cache(const std::string& function_signature); private: - static void parse_max_heap_memory_size_from_jvm(JNIEnv* env); - static Status GetJNIEnvSlowPath(JNIEnv** env); - static Status init_jni_scanner_loader(JNIEnv* env); + // Thread-local cache of the JNIEnv for this thread. + static __thread JNIEnv* tls_env_; - static bool jvm_inited_; - static jclass internal_exc_cl_; - static jclass jni_native_method_exc_cl_; + //for exception static jclass jni_util_cl_; static jmethodID throwable_to_string_id_; static jmethodID throwable_to_stack_trace_id_; - static jmethodID get_jvm_metrics_id_; - static jmethodID get_jvm_threads_id_; - static jmethodID get_jmx_json_; - // JNI scanner loader - static jobject jni_scanner_loader_obj_; - static jmethodID jni_scanner_loader_method_; - // Thread-local cache of the JNIEnv for this thread. - static __thread JNIEnv* tls_env_; - static jlong max_jvm_heap_memory_size_; - static jmethodID _clean_udf_cache_method_id; }; -/// Helper class for lifetime management of chars from JNI, releasing JNI chars when -/// destructed -class JniUtfCharGuard { +enum RefType { Local, Global }; + +enum BufferType { Chars, ByteArray }; + +template +struct RefHelper {}; + +template <> +struct RefHelper { + static jobject create(JNIEnv* env, jobject obj) { return env->NewLocalRef(obj); } + + static void destroy(JNIEnv* env, jobject obj) { env->DeleteLocalRef(obj); } + + static Status get_env(JNIEnv** env) { + // Get the JNIEnv* corresponding to current thread. + return Jni::Env::Get(env); + } +}; + +template <> +struct RefHelper { + static jobject create(JNIEnv* env, jobject obj) { return env->NewGlobalRef(obj); } + + static void destroy(JNIEnv* env, jobject obj) { env->DeleteGlobalRef(obj); } + + static Status get_env(JNIEnv** env) { return Jni::Env::Get(env); } +}; + +template +class Object; + +template +class Class; + +class MethodId { +public: + MethodId() = default; + bool uninitialized() const { return _id == nullptr; } + + template + friend class Object; + + template + friend class Class; + +private: + jmethodID _id = nullptr; +}; + +class FieldId { +public: + FieldId() = default; + bool uninitialized() const { return _id == nullptr; } + + template + friend class Object; + + template + friend class Class; + +private: + jfieldID _id = nullptr; +}; + +enum CallTag { + ObjectMethod, + IntMethod, + LongMethod, + VoidMethod, + BooleanMethod, + + StaticObjectMethod, + StaticIntMethod, + StaticLongMethod, + StaticVoidMethod, + + NewObject, + + NonvirtualVoidMethod, + NonvirtualObjectMethod, + NonvirtualIntMethod, + NonvirtualBooleanMethod, +}; + +template +struct CallHelper {}; + +template <> +struct CallHelper { + static jobject call_impl(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + return env->CallObjectMethodA(obj, methodID, args); + } + using BASE_TYPE = jobject; + using RETURN_TYPE = jobject; +}; + +template <> +struct CallHelper { + static jint call_impl(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + return env->CallIntMethodA(obj, methodID, args); + } + using BASE_TYPE = jobject; + using RETURN_TYPE = jint; +}; + +template <> +struct CallHelper { + static jlong call_impl(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + return env->CallLongMethodA(obj, methodID, args); + } + using BASE_TYPE = jobject; + using RETURN_TYPE = jlong; +}; + +template <> +struct CallHelper { + static void call_impl(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + env->CallVoidMethodA(obj, methodID, args); + } + using BASE_TYPE = jobject; + using RETURN_TYPE = void; +}; + +template <> +struct CallHelper { + static jboolean call_impl(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + return env->CallBooleanMethodA(obj, methodID, args); + } + using BASE_TYPE = jobject; + using RETURN_TYPE = jboolean; +}; + +template <> +struct CallHelper { + static jobject call_impl(JNIEnv* env, jclass cls, jmethodID methodID, const jvalue* args) { + return env->CallStaticObjectMethodA(cls, methodID, args); + } + using BASE_TYPE = jclass; + using RETURN_TYPE = jobject; +}; + +template <> +struct CallHelper { + static jint call_impl(JNIEnv* env, jclass cls, jmethodID methodID, const jvalue* args) { + return env->CallStaticIntMethodA(cls, methodID, args); + } + using BASE_TYPE = jclass; + using RETURN_TYPE = jint; +}; + +template <> +struct CallHelper { + static jlong call_impl(JNIEnv* env, jclass cls, jmethodID methodID, const jvalue* args) { + return env->CallStaticLongMethodA(cls, methodID, args); + } + using BASE_TYPE = jclass; + using RETURN_TYPE = jlong; +}; + +template <> +struct CallHelper { + static void call_impl(JNIEnv* env, jclass cls, jmethodID methodID, const jvalue* args) { + return env->CallStaticVoidMethodA(cls, methodID, args); + } + + using BASE_TYPE = jclass; + using RETURN_TYPE = void; +}; + +template <> +struct CallHelper { + static jobject call_impl(JNIEnv* env, jclass cls, jmethodID methodID, const jvalue* args) { + return env->NewObjectA(cls, methodID, args); + } + + using BASE_TYPE = jclass; + using RETURN_TYPE = jobject; +}; + +template <> +struct CallHelper { + static void call_impl(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue* args) { + return env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); + } + + using BASE_TYPE = jobject; + using RETURN_TYPE = void; +}; + +template <> +struct CallHelper { + static jobject call_impl(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue* args) { + return env->CallNonvirtualObjectMethodA(obj, clazz, methodID, args); + } + + using BASE_TYPE = jobject; + using RETURN_TYPE = jobject; +}; + +template <> +struct CallHelper { + static jint call_impl(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue* args) { + return env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); + } + + using BASE_TYPE = jobject; + using RETURN_TYPE = jint; +}; + +template <> +struct CallHelper { + static jboolean call_impl(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue* args) { + return env->CallNonvirtualBooleanMethodA(obj, clazz, methodID, args); + } + + using BASE_TYPE = jobject; + using RETURN_TYPE = jboolean; +}; + +template +class FunctionCall { +public: + FunctionCall(FunctionCall&& other) noexcept = default; + + static FunctionCall instance(JNIEnv* env, typename CallHelper::BASE_TYPE base, + jmethodID method_id) { + return FunctionCall(env, base, method_id); + } + + /// Pass a primitive arg (eg an integer). + /// Multiple arguments may be passed by repeated calls. + template + requires std::disjunction_v, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same> + FunctionCall& with_arg(T arg) { + jvalue v; + std::memset(&v, 0, sizeof(v)); + if constexpr (std::is_same_v) { + v.z = arg; + } else if constexpr (std::is_same_v) { + v.b = arg; + } else if constexpr (std::is_same_v) { + v.c = arg; + } else if constexpr (std::is_same_v) { + v.s = arg; + } else if constexpr (std::is_same_v) { + v.i = arg; + } else if constexpr (std::is_same_v) { + v.j = arg; + } else if constexpr (std::is_same_v) { + v.f = arg; + } else if constexpr (std::is_same_v) { + v.d = arg; + } else { + static_assert(false); + } + _args.push_back(v); + return *this; + } + + template + FunctionCall& with_arg(const Object& obj) WARN_UNUSED_RESULT; + + template + requires(std::is_same_v::RETURN_TYPE, ReturnType>) + Status call(ReturnType* result) { + *result = CallHelper::call_impl(_env, _base, _method, _args.data()); + RETURN_ERROR_IF_EXC(_env); + return Status::OK(); + } + + Status call(Object* result); + + Status call(Object* result); + + Status call() { + using return_type = typename CallHelper::RETURN_TYPE; + if constexpr (std::disjunction_v< + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same>) { + CallHelper::call_impl(_env, _base, _method, _args.data()); + RETURN_ERROR_IF_EXC(_env); + } else if constexpr (std::is_same_v) { + jobject tmp = CallHelper::call_impl(_env, _base, _method, _args.data()); + _env->DeleteLocalRef(tmp); + RETURN_ERROR_IF_EXC(_env); + } else { + static_assert(false); + } + return Status::OK(); + } + +protected: + explicit FunctionCall(JNIEnv* env, typename CallHelper::BASE_TYPE base, + jmethodID method_id) + : _env(env), _base(base), _method(method_id) {} + + JNIEnv* _env = nullptr; + CallHelper::BASE_TYPE _base; // is jobject/jclass not need new/delete local/global ref. + const jmethodID _method = nullptr; + std::vector _args; + Status _st = Status::OK(); + DISALLOW_COPY_AND_ASSIGN(FunctionCall); +}; + +template +class NonvirtualFunctionCall : public FunctionCall { public: - /// Construct a JniUtfCharGuards holding nothing - JniUtfCharGuard() : utf_chars(nullptr) {} + NonvirtualFunctionCall(NonvirtualFunctionCall&& other) noexcept = default; + + static NonvirtualFunctionCall instance(JNIEnv* env, typename CallHelper::BASE_TYPE base, + jclass cls, jmethodID method_id) { + return NonvirtualFunctionCall(env, base, cls, method_id); + } + + // no override + template + requires std::disjunction_v, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same> + NonvirtualFunctionCall& with_arg(T arg) { + jvalue v; + std::memset(&v, 0, sizeof(v)); + if constexpr (std::is_same_v) { + v.z = arg; + } else if constexpr (std::is_same_v) { + v.b = arg; + } else if constexpr (std::is_same_v) { + v.c = arg; + } else if constexpr (std::is_same_v) { + v.s = arg; + } else if constexpr (std::is_same_v) { + v.i = arg; + } else if constexpr (std::is_same_v) { + v.j = arg; + } else if constexpr (std::is_same_v) { + v.f = arg; + } else if constexpr (std::is_same_v) { + v.d = arg; + } else { + static_assert(false); + } + this->_args.push_back(v); + return *this; + } + + template + NonvirtualFunctionCall& with_arg(const Object& obj) WARN_UNUSED_RESULT; - /// Release the held char sequence if there is one. - ~JniUtfCharGuard() { - if (utf_chars != nullptr) env->ReleaseStringUTFChars(jstr, utf_chars); + // no override + Status call() { + using return_type = typename CallHelper::RETURN_TYPE; + if constexpr (std::disjunction_v< + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same, std::is_same, + std::is_same>) { + CallHelper::call_impl(this->_env, this->_base, _cls, this->_method, + this->_args.data()); + RETURN_ERROR_IF_EXC(this->_env); + } else if constexpr (std::is_same_v) { + jobject tmp = CallHelper::call_impl(this->_env, this->_base, _cls, this->_method, + this->_args.data()); + RETURN_ERROR_IF_EXC(this->_env); + this->_env->DeleteLocalRef(tmp); + } else { + static_assert(false); + } + return Status::OK(); } - /// Try to get chars from jstr. If error is returned, utf_chars and get() remain - /// to be nullptr, otherwise they point to a valid char sequence. The char sequence - /// lives as long as this guard. jstr should not be null. - static Status create(JNIEnv* env, jstring jstr, JniUtfCharGuard* out); + Status call(Object* result); - /// Get the char sequence. Returns nullptr if the guard does hold a char sequence. - const char* get() { return utf_chars; } + template + requires(std::is_same_v::RETURN_TYPE, ReturnType>) + Status call(ReturnType* result) { + *result = CallHelper::call_impl(this->_env, this->_base, _cls, this->_method, + this->_args.data()); + RETURN_ERROR_IF_EXC(this->_env); + return Status::OK(); + } private: - JNIEnv* env = nullptr; - jstring jstr; - const char* utf_chars = nullptr; - DISALLOW_COPY_AND_ASSIGN(JniUtfCharGuard); + explicit NonvirtualFunctionCall(JNIEnv* env, typename CallHelper::BASE_TYPE base, + jclass cls, jmethodID method_id) + : FunctionCall(env, base, method_id), _cls(cls) {} + jclass _cls; + DISALLOW_COPY_AND_ASSIGN(NonvirtualFunctionCall); }; -class JniLocalFrame { +/** + * When writing JNI code, developers usually need to pay extra attention to several error-prone aspects, including: + * 1. The reference type of jobject (local vs. global) + * 2. The lifetime and scope of JNI references + * 3. Proper release of references after use + * 4. Explicit exception checking after JNI calls + * Because these concerns are verbose and easy to overlook, they often lead to bugs or inconsistent code. To simplify this, + * we provide a wrapper framework around raw JNI APIs. The following describes how to use it (assuming the user already + * understands the basic JNI programming model). + * + * 0. Get JNIEnv* env: `Status st = Jni::Env::Get(&env)` + * 1. Choose the reference type + * First, determine whether the JNI object should be a local or global reference. Based on this, create the corresponding C++ wrapper object: + * LocalObject / GlobalObject. If the exact JNI type is known, use specialized wrappers such as . + * 2. Initialize the object + * For `jclass`, typically use: `Status st = Jni::Util::find_class(xxx);` + * For other object types, they are usually initialized via: `Status st = clazz.new_object(xxx).with_arg(xxx).call(&object) or by calling methods on existing objects. + * 3. Call methods and retrieve results + * To invoke a method and obtain a return value, use: `Status st = object.call__method(xxx).call(&result);` + * + * Notes + * 1. All JNI references are automatically released in the wrapper’s destructor, ensuring safe and deterministic cleanup. + * 2. All framework method invocations return a Status. + * The actual JNI return value is written to the address passed to call(). + * + * Example: be/test/util/jni_util_test.cpp +*/ +template +class Object { + // env->GetObjectRefType public: - JniLocalFrame() : env_(nullptr) {} - ~JniLocalFrame() { - if (env_ != nullptr) env_->PopLocalFrame(nullptr); + Object() = default; + + template + friend class Object; + + template + friend class Class; + + template + friend class String; + + template + friend class Array; + + template + friend class BufferGuard; + + template + friend class FunctionCall; + + template + friend class NonvirtualFunctionCall; + + virtual ~Object() { + if (_obj != nullptr) [[likely]] { + JNIEnv* env = nullptr; + if (Status st = RefHelper::get_env(&env); !st.ok()) [[unlikely]] { + LOG(WARNING) << "Can't destroy Jni Ref : " << st.msg(); + return; + } + RefHelper::destroy(env, _obj); + } + } + + template + static Status create(JNIEnv* env, const Object& other, Object* result) { + DCHECK(!other.uninitialized()); + DCHECK(result->uninitialized()); + + result->_obj = RefHelper::create(env, other._obj); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + bool uninitialized() const { return _obj == nullptr; } + + template + bool equal(JNIEnv* env, const Object& other) { + DCHECK(!uninitialized()); + DCHECK(!other.uninitialized()); + return env->IsSameObject(this->_obj, other._obj); //assume not throw exception. + } + + FunctionCall call_object_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, _obj, method_id._id); + } + + FunctionCall call_int_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, _obj, method_id._id); + } + + FunctionCall call_long_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, _obj, method_id._id); + } + + FunctionCall call_void_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, _obj, method_id._id); + } + + FunctionCall call_boolean_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, _obj, method_id._id); } - JniLocalFrame(JniLocalFrame&& other) noexcept : env_(other.env_) { other.env_ = nullptr; } + template + NonvirtualFunctionCall call_nonvirtual_void_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const; - /// Pushes a new JNI local frame. The frame can support max_local_ref local references. - /// The number of local references created inside the frame might exceed max_local_ref, - /// but there is no guarantee that memory will be available. - /// Push should be called at most once. - Status push(JNIEnv* env, int max_local_ref = 10) WARN_UNUSED_RESULT; + template + NonvirtualFunctionCall call_nonvirtual_object_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const; + + template + NonvirtualFunctionCall call_nonvirtual_int_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const; + + template + NonvirtualFunctionCall call_nonvirtual_boolean_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const; + +protected: + jobject _obj = nullptr; + DISALLOW_COPY_AND_ASSIGN(Object); +}; + +using LocalObject = Object; +using GlobalObject = Object; + +static inline Status local_to_global_ref(JNIEnv* env, const LocalObject& local_ref, + GlobalObject* global_ref) { + return Object::create(env, local_ref, global_ref); +} + +// auto ReleaseStringUTFChars ReleaseByteArrayElements ... +template +class BufferGuard { +public: + BufferGuard() = default; + + template + static Status create(JNIEnv* env, const Object& object, + BufferGuard* result, jboolean* isCopy) { + DCHECK(result->_buffer == nullptr && result->_object.uninitialized()); + + RETURN_IF_ERROR(Object::create(env, object, &result->_object)); + + if constexpr (bufferfType == BufferType::Chars) { + result->_buffer = env->GetStringUTFChars((jstring)result->_object._obj, isCopy); + } else if constexpr (bufferfType == BufferType::ByteArray) { + result->_buffer = + (char*)env->GetByteArrayElements((jbyteArray)result->_object._obj, isCopy); + } else { + static_assert(false); + } + + RETURN_ERROR_IF_EXC(env); + if (result->_buffer == nullptr) [[unlikely]] { + return Status::JniError("GetStringUTFChars/GetByteArrayElements fail."); + } + + return Status::OK(); + } + + ~BufferGuard() { + if (_object.uninitialized() || _buffer == nullptr) [[unlikely]] { + return; + } + JNIEnv* env = nullptr; + + if (auto st = RefHelper::get_env(&env); !st.ok()) [[unlikely]] { + LOG(WARNING) << "BufferGuard release fail: " << st; + return; + } + + if constexpr (bufferfType == BufferType::Chars) { + env->ReleaseStringUTFChars((jstring)_object._obj, _buffer); + } else if constexpr (bufferfType == BufferType::ByteArray) { + env->ReleaseByteArrayElements((jbyteArray)_object._obj, (jbyte*)_buffer, JNI_ABORT); + } + } + + const char* get() const { return _buffer; } + +private: + Object _object; + const char* _buffer = nullptr; + + DISALLOW_COPY_AND_ASSIGN(BufferGuard); +}; + +template +using StringBufferGuard = BufferGuard; +using LocalStringBufferGuard = BufferGuard; +using GlobalStringBufferGuard = BufferGuard; + +template +using ByteArrayBufferGuard = BufferGuard; +using LocalByteArrayBufferGuard = BufferGuard; +using GlobalByteArrayBufferGuard = BufferGuard; + +template +class String : public Object { +public: + String() = default; + + static Status new_string(JNIEnv* env, const char* utf_chars, String* result) { + DCHECK(result->uninitialized()); + + if constexpr (Ref == Local) { + result->_obj = env->NewStringUTF(utf_chars); + RETURN_ERROR_IF_EXC(env); + } else if constexpr (Ref == Global) { + String local_result; + local_result->_obj = env->NewStringUTF(utf_chars); + RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(local_to_global_ref(env, local_result, result)); + } else { + static_assert(false); + } + return Status::OK(); + } + + Status get_string_chars(JNIEnv* env, StringBufferGuard* jni_chars) const { + return StringBufferGuard::create(env, *this, jni_chars, nullptr); + } private: - DISALLOW_COPY_AND_ASSIGN(JniLocalFrame); + DISALLOW_COPY_AND_ASSIGN(String); +}; + +template +class Array : public Object { +public: + Array() = default; + + Status get_length(JNIEnv* env, jsize* result) const { + DCHECK(!this->uninitialized()); + + *result = env->GetArrayLength((jarray)this->_obj); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + Status get_object_array_element(JNIEnv* env, jsize index, Jni::LocalObject* result) { + DCHECK(!this->uninitialized()); + DCHECK(result->uninitialized()); + result->_obj = env->GetObjectArrayElement((jobjectArray)this->_obj, index); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + Status get_byte_elements(JNIEnv* env, ByteArrayBufferGuard* jni_bytes) const { + DCHECK(!this->uninitialized()); + return ByteArrayBufferGuard::create(env, *this, jni_bytes, nullptr); + } + + Status get_byte_elements(JNIEnv* env, jsize start, jsize len, jbyte* buffer) { + DCHECK(!this->uninitialized()); + env->GetByteArrayRegion((jbyteArray)this->_obj, start, len, + reinterpret_cast(buffer)); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } - JNIEnv* env_ = nullptr; + static Status WriteBufferToByteArray(JNIEnv* env, const jbyte* buffer, jint size, + Array* serialized_msg) { + DCHECK(serialized_msg->uninitialized()); + /// create jbyteArray given buffer + serialized_msg->_obj = env->NewByteArray(size); + RETURN_ERROR_IF_EXC(env); + if (serialized_msg->_obj == nullptr) [[unlikely]] { + return Status::JniError("couldn't construct jbyteArray"); + } + env->SetByteArrayRegion((jbyteArray)serialized_msg->_obj, 0, size, buffer); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + template + static Status SerializeThriftMsg(JNIEnv* env, T* msg, Array* serialized_msg) { + int buffer_size = 100 * 1024; // start out with 100KB + ThriftSerializer serializer(false, buffer_size); + + uint8_t* buffer = nullptr; + uint32_t size = 0; + RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer)); + + // Make sure that 'size' is within the limit of INT_MAX as the use of + // 'size' below takes int. + if (size > INT_MAX) [[unlikely]] { + return Status::JniError( + "The length of the serialization buffer ({} bytes) exceeds the limit of {} " + "bytes", + size, INT_MAX); + } + RETURN_IF_ERROR(WriteBufferToByteArray(env, (jbyte*)buffer, size, serialized_msg)); + return Status::OK(); + } + +private: + DISALLOW_COPY_AND_ASSIGN(Array); }; -template -Status SerializeThriftMsg(JNIEnv* env, T* msg, jbyteArray* serialized_msg) { - int buffer_size = 100 * 1024; // start out with 100KB - ThriftSerializer serializer(false, buffer_size); +template +class Class : public Object { +public: + Class() = default; - uint8_t* buffer = nullptr; - uint32_t size = 0; - RETURN_IF_ERROR(serializer.serialize(msg, &size, &buffer)); + static Status find_class(JNIEnv* env, const char* class_str, Class* result) { + DCHECK(result->uninitialized()); + if constexpr (Ref == Local) { + result->_obj = env->FindClass(class_str); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } else if constexpr (Ref == Global) { + Class local_class; + local_class._obj = env->FindClass(class_str); + RETURN_ERROR_IF_EXC(env); + return local_to_global_ref(env, local_class, result); + } else { + static_assert(false); + } + } - // Make sure that 'size' is within the limit of INT_MAX as the use of - // 'size' below takes int. - if (size > INT_MAX) { - return Status::JniError( - "The length of the serialization buffer ({} bytes) exceeds the limit of {} bytes", - size, INT_MAX); + Status get_static_method(JNIEnv* env, const char* method_str, const char* method_signature, + MethodId* method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(method_id->uninitialized()); + method_id->_id = env->GetStaticMethodID((jclass)this->_obj, method_str, method_signature); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); } - /// create jbyteArray given buffer - *serialized_msg = env->NewByteArray(size); - RETURN_ERROR_IF_EXC(env); - if (*serialized_msg == nullptr) { - return Status::JniError("couldn't construct jbyteArray"); + Status get_method(JNIEnv* env, const char* method_str, const char* method_signature, + MethodId* method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(method_id->uninitialized()); + method_id->_id = env->GetMethodID((jclass)this->_obj, method_str, method_signature); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + Status get_static_fieldId(JNIEnv* env, const char* name, const char* signature, + FieldId* field_id) const { + DCHECK(!this->uninitialized()); + DCHECK(field_id->uninitialized()); + field_id->_id = env->GetStaticFieldID((jclass)this->_obj, name, signature); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + Status get_static_object_field(JNIEnv* env, const FieldId& field_id, + Object* result) const { + DCHECK(!this->uninitialized()); + DCHECK(!field_id.uninitialized()); + result->_obj = env->GetStaticObjectField((jclass)this->_obj, field_id._id); + RETURN_ERROR_IF_EXC(env); + return Status::OK(); + } + + Status get_static_object_field(JNIEnv* env, const FieldId& field_id, + Object* global_result) const { + DCHECK(!this->uninitialized()); + DCHECK(!field_id.uninitialized()); + Object local_result; + local_result._obj = env->GetStaticObjectField((jclass)this->_obj, field_id._id); + RETURN_ERROR_IF_EXC(env); + return local_to_global_ref(env, local_result, global_result); + } + + Status get_static_object_field(JNIEnv* env, const char* name, const char* signature, + Object* global_result) const { + Jni::FieldId tmpFieldID; + RETURN_IF_ERROR(get_static_fieldId(env, name, signature, &tmpFieldID)); + RETURN_IF_ERROR(get_static_object_field(env, tmpFieldID, global_result)); + return Status::OK(); } - env->SetByteArrayRegion(*serialized_msg, 0, size, reinterpret_cast(buffer)); - RETURN_ERROR_IF_EXC(env); + + FunctionCall new_object(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, (jclass)this->_obj, method_id._id); + } + + FunctionCall call_static_object_method(JNIEnv* env, + MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, (jclass)this->_obj, method_id._id); + } + + FunctionCall call_static_void_method(JNIEnv* env, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return FunctionCall::instance(env, (jclass)this->_obj, method_id._id); + } + +private: + DISALLOW_COPY_AND_ASSIGN(Class); +}; + +using LocalClass = Class; +using GlobalClass = Class; + +using LocalArray = Array; +using GlobalArray = Array; + +using LocalString = String; +using GlobalString = String; + +template +template +FunctionCall& FunctionCall::with_arg(const Object& obj) { + jvalue v; + std::memset(&v, 0, sizeof(v)); + v.l = obj._obj; + _args.push_back(v); + return *this; +} + +template +template +NonvirtualFunctionCall& NonvirtualFunctionCall::with_arg(const Object& obj) { + jvalue v; + std::memset(&v, 0, sizeof(v)); + v.l = obj._obj; + this->_args.push_back(v); + return *this; +} + +template +Status FunctionCall::call(Object* result) { + DCHECK(result->uninitialized()); + result->_obj = CallHelper::call_impl(_env, _base, _method, _args.data()); + RETURN_ERROR_IF_EXC(this->_env); + return Status::OK(); +} + +template +Status FunctionCall::call(Object* result) { + DCHECK(result->uninitialized()); + Object local_result; + local_result._obj = CallHelper::call_impl(_env, _base, _method, _args.data()); + RETURN_ERROR_IF_EXC(this->_env); + return local_to_global_ref(_env, local_result, result); +} + +template +Status NonvirtualFunctionCall::call(Object* result) { + DCHECK(result->uninitialized()); + result->_obj = CallHelper::call_impl(this->_env, this->_base, _cls, this->_method, + this->_args.data()); + RETURN_ERROR_IF_EXC(this->_env); return Status::OK(); } +template +template +NonvirtualFunctionCall Object::call_nonvirtual_object_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + + return NonvirtualFunctionCall::instance(env, _obj, (jclass)clazz._obj, + method_id._id); +} + +template +template +NonvirtualFunctionCall Object::call_nonvirtual_void_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return NonvirtualFunctionCall::instance(env, _obj, (jclass)clazz._obj, + method_id._id); +} + +template +template +NonvirtualFunctionCall Object::call_nonvirtual_int_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return NonvirtualFunctionCall::instance(env, _obj, (jclass)clazz._obj, + method_id._id); +} + +template +template +NonvirtualFunctionCall Object::call_nonvirtual_boolean_method( + JNIEnv* env, const Class& clazz, MethodId method_id) const { + DCHECK(!this->uninitialized()); + DCHECK(!method_id.uninitialized()); + return NonvirtualFunctionCall::instance(env, _obj, (jclass)clazz._obj, + method_id._id); +} + +class Util { +public: + static size_t get_max_jni_heap_memory_size(); + + template + static Status find_class(JNIEnv* env, const char* class_str, Class* result) { + return Class::find_class(env, class_str, result); + } + + template + static Status WriteBufferToByteArray(JNIEnv* env, const jbyte* buffer, jint size, + Array* serialized_msg) { + if constexpr (Ref == Local) { + return Array::WriteBufferToByteArray(env, buffer, size, serialized_msg); + } else if constexpr (Ref == Global) { + Array local_obj; + RETURN_IF_ERROR(Array::WriteBufferToByteArray(env, buffer, size, &local_obj)); + return local_to_global_ref(env, local_obj, serialized_msg); + } else { + static_assert(false); + } + } + + template + static Status SerializeThriftMsg(JNIEnv* env, T* msg, Array* serialized_msg) { + if constexpr (Ref == Local) { + return Array::SerializeThriftMsg(env, msg, serialized_msg); + } else if (Ref == Global) { + Array local_obj; + RETURN_IF_ERROR(Array::SerializeThriftMsg(env, msg, local_obj)); + return local_to_global_ref(env, local_obj, serialized_msg); + } else { + static_assert(false); + } + } + + template + static Status get_jni_scanner_class(JNIEnv* env, const char* classname, + Object* jni_scanner_class) { + // Get JNI scanner class by class name; + LocalString class_name_str; + RETURN_IF_ERROR(LocalString::new_string(env, classname, &class_name_str)); + return jni_scanner_loader_obj_.call_object_method(env, jni_scanner_loader_method_) + .with_arg(class_name_str) + .call(jni_scanner_class); + } + + template + static Status convert_to_java_map(JNIEnv* env, const std::map& map, + Object* hashmap_object) { + RETURN_IF_ERROR(hashmap_class.new_object(env, hashmap_constructor) + .with_arg((jint)map.size()) + .call(hashmap_object)); + + for (const auto& it : map) { + LocalString key; + RETURN_IF_ERROR(String::new_string(env, it.first.c_str(), &key)); + + LocalString value; + RETURN_IF_ERROR(String::new_string(env, it.second.c_str(), &value)); + + LocalObject result; + RETURN_IF_ERROR(hashmap_object->call_object_method(env, hashmap_put) + .with_arg(key) + .with_arg(value) + .call()); + } + return Status::OK(); + } + + template + static Status convert_to_cpp_map(JNIEnv* env, const Object& map, + std::map* resultMap) { + LocalObject entrySet; + RETURN_IF_ERROR(map.call_object_method(env, mapEntrySetMethod).call(&entrySet)); + + // Call the iterator method on the set to iterate over the key-value pairs + LocalObject iteratorSet; + RETURN_IF_ERROR(entrySet.call_object_method(env, iteratorSetMethod).call(&iteratorSet)); + + while (true) { + jboolean hasNext = false; + RETURN_IF_ERROR( + iteratorSet.call_boolean_method(env, iteratorHasNextMethod).call(&hasNext)); + if (!hasNext) { + break; + } + + LocalObject entry; + RETURN_IF_ERROR(iteratorSet.call_object_method(env, iteratorNextMethod).call(&entry)); + + LocalString javaKey; + RETURN_IF_ERROR(entry.call_object_method(env, getEntryKeyMethod).call(&javaKey)); + + LocalString javaValue; + RETURN_IF_ERROR(entry.call_object_method(env, getEntryValueMethod).call(&javaValue)); + + LocalStringBufferGuard key; + RETURN_IF_ERROR(javaKey.get_string_chars(env, &key)); + + LocalStringBufferGuard value; + RETURN_IF_ERROR(javaValue.get_string_chars(env, &value)); + + // Store the key-value pair in the map + (*resultMap)[key.get()] = value.get(); + } + return Status::OK(); + } + + static Status clean_udf_class_load_cache(const std::string& function_signature); + + static Status Init(); + +private: + static void _parse_max_heap_memory_size_from_jvm(); + + static Status _init_collect_class() WARN_UNUSED_RESULT; + static Status _init_register_natives() WARN_UNUSED_RESULT; + static Status _init_jni_scanner_loader() WARN_UNUSED_RESULT; + + static bool jvm_inited_; + + // for jvm heap + static jlong max_jvm_heap_memory_size_; + + // for JNI scanner loader + static GlobalObject jni_scanner_loader_obj_; + static MethodId jni_scanner_loader_method_; + + // for clean udf cache + static MethodId _clean_udf_cache_method_id; + + //for hashmap + static GlobalClass hashmap_class; + static MethodId hashmap_constructor; + static MethodId hashmap_put; + + //for map + static GlobalClass mapClass; + static MethodId mapEntrySetMethod; + + // for map entry + static GlobalClass mapEntryClass; + static MethodId getEntryKeyMethod; + static MethodId getEntryValueMethod; + + //for set + static GlobalClass setClass; + static MethodId iteratorSetMethod; + + // for iterator + static GlobalClass iteratorClass; + static MethodId iteratorHasNextMethod; + static MethodId iteratorNextMethod; +}; +}; // namespace Jni + } // namespace doris diff --git a/be/src/util/jvm_metrics.cpp b/be/src/util/jvm_metrics.cpp index 1526451828593c..d722272bfb7e3f 100644 --- a/be/src/util/jvm_metrics.cpp +++ b/be/src/util/jvm_metrics.cpp @@ -80,7 +80,7 @@ DEFINE_COUNTER_METRIC_PROTOTYPE_5ARG(jvm_gc_g1_old_generation_time_ms, MetricUni const char* JvmMetrics::_s_hook_name = "jvm_metrics"; -JvmMetrics::JvmMetrics(MetricRegistry* registry, JNIEnv* env) { +JvmMetrics::JvmMetrics(MetricRegistry* registry) { DCHECK(registry != nullptr); _registry = registry; @@ -92,7 +92,7 @@ JvmMetrics::JvmMetrics(MetricRegistry* registry, JNIEnv* env) { break; } try { - Status st = _jvm_stats.init(env); + Status st = _jvm_stats.init(); if (!st) { LOG(WARNING) << "jvm Stats Init Fail. " << st.to_string(); break; @@ -200,170 +200,98 @@ void JvmMetrics::update() { } } -Status JvmStats::init(JNIEnv* env) { - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/ManagementFactory", - &_managementFactoryClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryMXBeanMethod, env, - GetStaticMethodID(_managementFactoryClass, "getMemoryMXBean", - "()Ljava/lang/management/MemoryMXBean;")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/MemoryUsage", - &_memoryUsageClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryUsageUsedMethod, env, - GetMethodID(_memoryUsageClass, "getUsed", "()J")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryUsageCommittedMethod, env, - GetMethodID(_memoryUsageClass, "getCommitted", "()J")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryUsageMaxMethod, env, - GetMethodID(_memoryUsageClass, "getMax", "()J")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/MemoryMXBean", - &_memoryMXBeanClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getHeapMemoryUsageMethod, env, - GetMethodID(_memoryMXBeanClass, "getHeapMemoryUsage", - "()Ljava/lang/management/MemoryUsage;")); - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getNonHeapMemoryUsageMethod, env, - GetMethodID(_memoryMXBeanClass, "getNonHeapMemoryUsage", - "()Ljava/lang/management/MemoryUsage;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getMemoryPoolMXBeansMethod, env, - GetStaticMethodID(_managementFactoryClass, "getMemoryPoolMXBeans", - "()Ljava/util/List;")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/util/List", &_listClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getListSizeMethod, env, - GetMethodID(_listClass, "size", "()I")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getListUseIndexMethod, env, - GetMethodID(_listClass, "get", "(I)Ljava/lang/Object;")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/MemoryPoolMXBean", - &_memoryPoolMXBeanClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryPoolMXBeanUsageMethod, env, - GetMethodID(_memoryPoolMXBeanClass, "getUsage", - "()Ljava/lang/management/MemoryUsage;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getMemoryPollMXBeanPeakMethod, env, - GetMethodID(_memoryPoolMXBeanClass, "getPeakUsage", - "()Ljava/lang/management/MemoryUsage;")); - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getMemoryPollMXBeanNameMethod, env, - GetMethodID(_memoryPoolMXBeanClass, "getName", "()Ljava/lang/String;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, _getThreadMXBeanMethod, env, - GetStaticMethodID(_managementFactoryClass, "getThreadMXBean", - "()Ljava/lang/management/ThreadMXBean;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getGarbageCollectorMXBeansMethod, env, - GetStaticMethodID(_managementFactoryClass, "getGarbageCollectorMXBeans", - "()Ljava/util/List;")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/GarbageCollectorMXBean", - &_garbageCollectorMXBeanClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getGCNameMethod, env, - GetMethodID(_garbageCollectorMXBeanClass, "getName", "()Ljava/lang/String;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getGCCollectionCountMethod, env, - GetMethodID(_garbageCollectorMXBeanClass, "getCollectionCount", "()J")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _getGCCollectionTimeMethod, env, - GetMethodID(_garbageCollectorMXBeanClass, "getCollectionTime", "()J")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/management/ThreadMXBean", - &_threadMXBeanClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, - - _getAllThreadIdsMethod, env, - GetMethodID(_threadMXBeanClass, "getAllThreadIds", "()[J")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, - - _getThreadInfoMethod, env, - GetMethodID(_threadMXBeanClass, "getThreadInfo", - "([JI)[Ljava/lang/management/ThreadInfo;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(, +Status JvmStats::init() { + JNIEnv* env = nullptr; + RETURN_IF_ERROR(Jni::Env::Get(&env)); - _getPeakThreadCountMethod, env, - GetMethodID(_threadMXBeanClass, "getPeakThreadCount", "()I")); + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/management/ManagementFactory", + &_managementFactoryClass)); + RETURN_IF_ERROR(_managementFactoryClass.get_static_method( + env, "getMemoryMXBean", "()Ljava/lang/management/MemoryMXBean;", + &_getMemoryMXBeanMethod)); RETURN_IF_ERROR( - JniUtil::GetGlobalClassRef(env, "java/lang/management/ThreadInfo", &_threadInfoClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , - - _getThreadStateMethod, env, - GetMethodID(_threadInfoClass, "getThreadState", "()Ljava/lang/Thread$State;")); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, "java/lang/Thread$State", &_threadStateClass)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, newThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "NEW", "Ljava/lang/Thread$State;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, runnableThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "RUNNABLE", "Ljava/lang/Thread$State;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, blockedThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "BLOCKED", "Ljava/lang/Thread$State;")); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, waitingThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "WAITING", "Ljava/lang/Thread$State;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, timedWaitingThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "TIMED_WAITING", "Ljava/lang/Thread$State;")); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jfieldID, terminatedThreadFieldID, env, - GetStaticFieldID(_threadStateClass, "TERMINATED", "Ljava/lang/Thread$State;")); - - JNI_CALL_METHOD_CHECK_EXCEPTION(jobject, newThreadStateObj, env, - GetStaticObjectField(_threadStateClass, newThreadFieldID)); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, newThreadStateObj, &_newThreadStateObj)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(jobject, runnableThreadStateObj, env, - GetStaticObjectField(_threadStateClass, runnableThreadFieldID)); + Jni::Util::find_class(env, "java/lang/management/MemoryUsage", &_memoryUsageClass)); RETURN_IF_ERROR( - JniUtil::LocalToGlobalRef(env, runnableThreadStateObj, &_runnableThreadStateObj)); + _memoryUsageClass.get_method(env, "getUsed", "()J", &_getMemoryUsageUsedMethod)); + RETURN_IF_ERROR(_memoryUsageClass.get_method(env, "getCommitted", "()J", + &_getMemoryUsageCommittedMethod)); + RETURN_IF_ERROR(_memoryUsageClass.get_method(env, "getMax", "()J", &_getMemoryUsageMaxMethod)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jobject, blockedThreadStateObj, env, - GetStaticObjectField(_threadStateClass, blockedThreadFieldID)); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, blockedThreadStateObj, &_blockedThreadStateObj)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(jobject, waitingThreadStateObj, env, - GetStaticObjectField(_threadStateClass, waitingThreadFieldID)); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, waitingThreadStateObj, &_waitingThreadStateObj)); + RETURN_IF_ERROR( + Jni::Util::find_class(env, "java/lang/management/MemoryMXBean", &_memoryMXBeanClass)); + RETURN_IF_ERROR(_memoryMXBeanClass.get_method(env, "getHeapMemoryUsage", + "()Ljava/lang/management/MemoryUsage;", + &_getHeapMemoryUsageMethod)); + RETURN_IF_ERROR(_memoryMXBeanClass.get_method(env, "getNonHeapMemoryUsage", + "()Ljava/lang/management/MemoryUsage;", + &_getNonHeapMemoryUsageMethod)); + + RETURN_IF_ERROR(_managementFactoryClass.get_static_method( + env, "getMemoryPoolMXBeans", "()Ljava/util/List;", &_getMemoryPoolMXBeansMethod)); + + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/util/List", &_listClass)); + RETURN_IF_ERROR(_listClass.get_method(env, "size", "()I", &_getListSizeMethod)); + RETURN_IF_ERROR( + _listClass.get_method(env, "get", "(I)Ljava/lang/Object;", &_getListUseIndexMethod)); + + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/management/MemoryPoolMXBean", + &_memoryPoolMXBeanClass)); + RETURN_IF_ERROR(_memoryPoolMXBeanClass.get_method(env, "getUsage", + "()Ljava/lang/management/MemoryUsage;", + &_getMemoryPoolMXBeanUsageMethod)); + RETURN_IF_ERROR(_memoryPoolMXBeanClass.get_method(env, "getPeakUsage", + "()Ljava/lang/management/MemoryUsage;", + &_getMemoryPoolMXBeanPeakMethod)); + RETURN_IF_ERROR(_memoryPoolMXBeanClass.get_method(env, "getName", "()Ljava/lang/String;", + &_getMemoryPoolMXBeanNameMethod)); + + RETURN_IF_ERROR(_managementFactoryClass.get_static_method( + env, "getThreadMXBean", "()Ljava/lang/management/ThreadMXBean;", + &_getThreadMXBeanMethod)); + RETURN_IF_ERROR(_managementFactoryClass.get_static_method(env, "getGarbageCollectorMXBeans", + "()Ljava/util/List;", + &_getGarbageCollectorMXBeansMethod)); + + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/management/GarbageCollectorMXBean", + &_garbageCollectorMXBeanClass)); + RETURN_IF_ERROR(_garbageCollectorMXBeanClass.get_method(env, "getName", "()Ljava/lang/String;", + &_getGCNameMethod)); + RETURN_IF_ERROR(_garbageCollectorMXBeanClass.get_method(env, "getCollectionCount", "()J", + &_getGCCollectionCountMethod)); + RETURN_IF_ERROR(_garbageCollectorMXBeanClass.get_method(env, "getCollectionTime", "()J", + &_getGCCollectionTimeMethod)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jobject, timedWaitingThreadStateObj, env, - GetStaticObjectField(_threadStateClass, timedWaitingThreadFieldID)); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, timedWaitingThreadStateObj, - &_timedWaitingThreadStateObj)); + RETURN_IF_ERROR( + Jni::Util::find_class(env, "java/lang/management/ThreadMXBean", &_threadMXBeanClass)); + RETURN_IF_ERROR( + _threadMXBeanClass.get_method(env, "getAllThreadIds", "()[J", &_getAllThreadIdsMethod)); + RETURN_IF_ERROR(_threadMXBeanClass.get_method(env, "getThreadInfo", + "([JI)[Ljava/lang/management/ThreadInfo;", + &_getThreadInfoMethod)); + RETURN_IF_ERROR(_threadMXBeanClass.get_method(env, "getPeakThreadCount", "()I", + &_getPeakThreadCountMethod)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jobject, terminatedThreadStateObj, env, - GetStaticObjectField(_threadStateClass, terminatedThreadFieldID)); RETURN_IF_ERROR( - JniUtil::LocalToGlobalRef(env, terminatedThreadStateObj, &_terminatedThreadStateObj)); + Jni::Util::find_class(env, "java/lang/management/ThreadInfo", &_threadInfoClass)); + RETURN_IF_ERROR(_threadInfoClass.get_method(env, "getThreadState", "()Ljava/lang/Thread$State;", + &_getThreadStateMethod)); + + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/Thread$State", &_threadStateClass)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "NEW", "Ljava/lang/Thread$State;", &_newThreadStateObj)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "RUNNABLE", "Ljava/lang/Thread$State;", &_runnableThreadStateObj)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "BLOCKED", "Ljava/lang/Thread$State;", &_blockedThreadStateObj)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "WAITING", "Ljava/lang/Thread$State;", &_waitingThreadStateObj)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "TIMED_WAITING", "Ljava/lang/Thread$State;", &_timedWaitingThreadStateObj)); + RETURN_IF_ERROR(_threadStateClass.get_static_object_field( + env, "TERMINATED", "Ljava/lang/Thread$State;", &_terminatedThreadStateObj)); _init_complete = true; - LOG(INFO) << "Start JVM monitoring."; return Status::OK(); } @@ -374,85 +302,90 @@ Status JvmStats::refresh(JvmMetrics* jvm_metrics) const { } JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, memoryMXBeanObj, env, - CallStaticObjectMethod(_managementFactoryClass, _getMemoryMXBeanMethod)); + Jni::LocalObject memoryMXBeanObj; + RETURN_IF_ERROR(_managementFactoryClass.call_static_object_method(env, _getMemoryMXBeanMethod) + .call(&memoryMXBeanObj)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, heapMemoryUsageObj, env, - CallObjectMethod(memoryMXBeanObj, _getHeapMemoryUsageMethod)); + Jni::LocalObject heapMemoryUsageObj; + RETURN_IF_ERROR(memoryMXBeanObj.call_object_method(env, _getHeapMemoryUsageMethod) + .call(&heapMemoryUsageObj)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, heapMemoryUsed, env, - CallLongMethod(heapMemoryUsageObj, _getMemoryUsageUsedMethod)); + jlong heapMemoryUsed = 0; + RETURN_IF_ERROR(heapMemoryUsageObj.call_long_method(env, _getMemoryUsageUsedMethod) + .call(&heapMemoryUsed)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jlong, heapMemoryCommitted, env, - CallLongMethod(heapMemoryUsageObj, _getMemoryUsageCommittedMethod)); + jlong heapMemoryCommitted = 0; + RETURN_IF_ERROR(heapMemoryUsageObj.call_long_method(env, _getMemoryUsageCommittedMethod) + .call(&heapMemoryCommitted)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, heapMemoryMax, env, - CallLongMethod(heapMemoryUsageObj, _getMemoryUsageMaxMethod)); + jlong heapMemoryMax = 0; + RETURN_IF_ERROR(heapMemoryUsageObj.call_long_method(env, _getMemoryUsageMaxMethod) + .call(&heapMemoryMax)); jvm_metrics->jvm_heap_size_bytes_used->set_value(heapMemoryUsed < 0 ? 0 : heapMemoryUsed); jvm_metrics->jvm_heap_size_bytes_committed->set_value( heapMemoryCommitted < 0 ? 0 : heapMemoryCommitted); jvm_metrics->jvm_heap_size_bytes_max->set_value(heapMemoryMax < 0 ? 0 : heapMemoryMax); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, nonHeapMemoryUsageObj, env, - CallObjectMethod(memoryMXBeanObj, _getNonHeapMemoryUsageMethod)); + Jni::LocalObject nonHeapMemoryUsageObj; + RETURN_IF_ERROR(memoryMXBeanObj.call_object_method(env, _getNonHeapMemoryUsageMethod) + .call(&nonHeapMemoryUsageObj)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jlong, nonHeapMemoryCommitted, env, - CallLongMethod(nonHeapMemoryUsageObj, _getMemoryUsageCommittedMethod)); + jlong nonHeapMemoryCommitted = 0; + RETURN_IF_ERROR(nonHeapMemoryUsageObj.call_long_method(env, _getMemoryUsageCommittedMethod) + .call(&nonHeapMemoryCommitted)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - jlong, nonHeapMemoryUsed, env, - CallLongMethod(nonHeapMemoryUsageObj, _getMemoryUsageUsedMethod)); + jlong nonHeapMemoryUsed = 0; + RETURN_IF_ERROR(nonHeapMemoryUsageObj.call_long_method(env, _getMemoryUsageUsedMethod) + .call(&nonHeapMemoryUsed)); jvm_metrics->jvm_non_heap_size_bytes_committed->set_value( nonHeapMemoryCommitted < 0 ? 0 : nonHeapMemoryCommitted); jvm_metrics->jvm_non_heap_size_bytes_used->set_value(nonHeapMemoryUsed < 0 ? 0 : nonHeapMemoryUsed); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, memoryPoolMXBeansList, env, - CallStaticObjectMethod(_managementFactoryClass, _getMemoryPoolMXBeansMethod)); + Jni::LocalObject memoryPoolMXBeansList; + RETURN_IF_ERROR( + _managementFactoryClass.call_static_object_method(env, _getMemoryPoolMXBeansMethod) + .call(&memoryPoolMXBeansList)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jint, size, env, - CallIntMethod(memoryPoolMXBeansList, _getListSizeMethod)); + jint beanSize = 0; + RETURN_IF_ERROR(memoryPoolMXBeansList.call_int_method(env, _getListSizeMethod).call(&beanSize)); - for (int i = 0; i < size; ++i) { - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, memoryPoolMXBean, env, - CallObjectMethod(memoryPoolMXBeansList, _getListUseIndexMethod, i)); + for (int i = 0; i < beanSize; ++i) { + Jni::LocalObject memoryPoolMXBean; + RETURN_IF_ERROR(memoryPoolMXBeansList.call_object_method(env, _getListUseIndexMethod) + .with_arg(i) + .call(&memoryPoolMXBean)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, usageObject, env, - CallObjectMethod(memoryPoolMXBean, _getMemoryPoolMXBeanUsageMethod)); + Jni::LocalObject usageObject; + RETURN_IF_ERROR(memoryPoolMXBean.call_object_method(env, _getMemoryPoolMXBeanUsageMethod) + .call(&usageObject)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, used, env, - CallLongMethod(usageObject, _getMemoryUsageUsedMethod)); + jlong used = 0; + RETURN_IF_ERROR(usageObject.call_long_method(env, _getMemoryUsageUsedMethod).call(&used)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, max, env, - CallLongMethod(usageObject, _getMemoryUsageMaxMethod)); + jlong max = 0; + RETURN_IF_ERROR(usageObject.call_long_method(env, _getMemoryUsageMaxMethod).call(&max)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, peakUsageObject, env, - CallObjectMethod(memoryPoolMXBean, _getMemoryPollMXBeanPeakMethod)); + Jni::LocalObject peakUsageObject; + RETURN_IF_ERROR(memoryPoolMXBean.call_object_method(env, _getMemoryPoolMXBeanPeakMethod) + .call(&peakUsageObject)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, peakUsed, env, - CallLongMethod(peakUsageObject, _getMemoryUsageUsedMethod)); + jlong peakUsed = 0; + RETURN_IF_ERROR( + peakUsageObject.call_long_method(env, _getMemoryUsageUsedMethod).call(&peakUsed)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, name, env, - CallObjectMethod(memoryPoolMXBean, _getMemoryPollMXBeanNameMethod)); + Jni::LocalString name; + RETURN_IF_ERROR(memoryPoolMXBean.call_object_method(env, _getMemoryPoolMXBeanNameMethod) + .call(&name)); - const char* nameStr = env->GetStringUTFChars( - (jstring)name, nullptr); // GetStringUTFChars not throw exception - if (nameStr != nullptr) { - auto it = _memoryPoolName.find(nameStr); + Jni::LocalStringBufferGuard nameStr; + RETURN_IF_ERROR(name.get_string_chars(env, &nameStr)); + if (nameStr.get() != nullptr) { + auto it = _memoryPoolName.find(nameStr.get()); if (it == _memoryPoolName.end()) { continue; } @@ -466,58 +399,56 @@ Status JvmStats::refresh(JvmMetrics* jvm_metrics) const { jvm_metrics->jvm_old_size_bytes_peak_used->set_value(peakUsed < 0 ? 0 : peakUsed); jvm_metrics->jvm_old_size_bytes_max->set_value(max < 0 ? 0 : max); } - - env->ReleaseStringUTFChars((jstring)name, - nameStr); // ReleaseStringUTFChars not throw exception } } - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, threadMXBean, env, - CallStaticObjectMethod(_managementFactoryClass, _getThreadMXBeanMethod)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, threadIdsObject, env, CallObjectMethod(threadMXBean, _getAllThreadIdsMethod)); + Jni::LocalObject threadMXBean; + RETURN_IF_ERROR(_managementFactoryClass.call_static_object_method(env, _getThreadMXBeanMethod) + .call(&threadMXBean)); - auto threadIds = (jlongArray)threadIdsObject; + Jni::LocalArray threadIds; + RETURN_IF_ERROR(threadMXBean.call_object_method(env, _getAllThreadIdsMethod).call(&threadIds)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jint, threadCount, env, GetArrayLength(threadIds)); + jsize threadCount = 0; + RETURN_IF_ERROR(threadIds.get_length(env, &threadCount)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, threadInfos, env, - CallObjectMethod(threadMXBean, _getThreadInfoMethod, (jlongArray)threadIds, 0)); + Jni::LocalArray threadInfos; + RETURN_IF_ERROR(threadMXBean.call_object_method(env, _getThreadInfoMethod) + .with_arg(threadIds) + .with_arg(0) + .call(&threadInfos)); int threadsNew = 0, threadsRunnable = 0, threadsBlocked = 0, threadsWaiting = 0, threadsTimedWaiting = 0, threadsTerminated = 0; - JNI_CALL_METHOD_CHECK_EXCEPTION(jint, peakThreadCount, env, - CallIntMethod(threadMXBean, _getPeakThreadCountMethod)); + jint peakThreadCount = 0; + RETURN_IF_ERROR( + threadMXBean.call_int_method(env, _getPeakThreadCountMethod).call(&peakThreadCount)); jvm_metrics->jvm_thread_peak_count->set_value(peakThreadCount < 0 ? 0 : peakThreadCount); jvm_metrics->jvm_thread_count->set_value(threadCount < 0 ? 0 : threadCount); for (int i = 0; i < threadCount; i++) { - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, threadInfo, env, GetObjectArrayElement((jobjectArray)threadInfos, i)); - - if (threadInfo == nullptr) { + Jni::LocalObject threadInfo; + RETURN_IF_ERROR(threadInfos.get_object_array_element(env, i, &threadInfo)); + if (threadInfo.uninitialized()) { continue; } + Jni::LocalObject threadState; + RETURN_IF_ERROR( + threadInfo.call_object_method(env, _getThreadStateMethod).call(&threadState)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, threadState, env, CallObjectMethod(threadInfo, _getThreadStateMethod)); - - //IsSameObject not throw exception - if (env->IsSameObject(threadState, _newThreadStateObj)) { + if (threadState.equal(env, _newThreadStateObj)) { threadsNew++; - } else if (env->IsSameObject(threadState, _runnableThreadStateObj)) { + } else if (threadState.equal(env, _runnableThreadStateObj)) { threadsRunnable++; - } else if (env->IsSameObject(threadState, _blockedThreadStateObj)) { + } else if (threadState.equal(env, _blockedThreadStateObj)) { threadsBlocked++; - } else if (env->IsSameObject(threadState, _waitingThreadStateObj)) { + } else if (threadState.equal(env, _waitingThreadStateObj)) { threadsWaiting++; - } else if (env->IsSameObject(threadState, _timedWaitingThreadStateObj)) { + } else if (threadState.equal(env, _timedWaitingThreadStateObj)) { threadsTimedWaiting++; - } else if (env->IsSameObject(threadState, _terminatedThreadStateObj)) { + } else if (threadState.equal(env, _terminatedThreadStateObj)) { threadsTerminated++; } } @@ -531,29 +462,35 @@ Status JvmStats::refresh(JvmMetrics* jvm_metrics) const { jvm_metrics->jvm_thread_terminated_count->set_value(threadsTerminated < 0 ? 0 : threadsTerminated); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, gcMXBeansList, env, - CallStaticObjectMethod(_managementFactoryClass, _getGarbageCollectorMXBeansMethod)); - - JNI_CALL_METHOD_CHECK_EXCEPTION(jint, numCollectors, env, - CallIntMethod(gcMXBeansList, _getListSizeMethod)); + Jni::LocalObject gcMXBeansList; + RETURN_IF_ERROR(_managementFactoryClass + .call_static_object_method(env, _getGarbageCollectorMXBeansMethod) + .call(&gcMXBeansList)); + jint numCollectors = 0; + RETURN_IF_ERROR(gcMXBeansList.call_int_method(env, _getListSizeMethod).call(&numCollectors)); for (int i = 0; i < numCollectors; i++) { - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, gcMXBean, env, CallObjectMethod(gcMXBeansList, _getListUseIndexMethod, i)); + Jni::LocalObject gcMXBean; + RETURN_IF_ERROR(gcMXBeansList.call_object_method(env, _getListUseIndexMethod) + .with_arg(i) + .call(&gcMXBean)); + + Jni::LocalString gcName; + RETURN_IF_ERROR(gcMXBean.call_object_method(env, _getGCNameMethod).call(&gcName)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF(jobject, gcName, env, - CallObjectMethod(gcMXBean, _getGCNameMethod)); + jlong gcCollectionCount = 0; + RETURN_IF_ERROR(gcMXBean.call_long_method(env, _getGCCollectionCountMethod) + .call(&gcCollectionCount)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, gcCollectionCount, env, - CallLongMethod(gcMXBean, _getGCCollectionCountMethod)); + jlong gcCollectionTime = 0; + RETURN_IF_ERROR( + gcMXBean.call_long_method(env, _getGCCollectionTimeMethod).call(&gcCollectionTime)); - JNI_CALL_METHOD_CHECK_EXCEPTION(jlong, gcCollectionTime, env, - CallLongMethod(gcMXBean, _getGCCollectionTimeMethod)); + Jni::LocalStringBufferGuard gcNameStr; + RETURN_IF_ERROR(gcName.get_string_chars(env, &gcNameStr)); - const char* gcNameStr = env->GetStringUTFChars((jstring)gcName, NULL); - if (gcNameStr != nullptr) { - if (strcmp(gcNameStr, "G1 Young Generation") == 0) { + if (gcNameStr.get() != nullptr) { + if (strcmp(gcNameStr.get(), "G1 Young Generation") == 0) { jvm_metrics->jvm_gc_g1_young_generation_count->set_value(gcCollectionCount); jvm_metrics->jvm_gc_g1_young_generation_time_ms->set_value(gcCollectionTime); @@ -561,43 +498,11 @@ Status JvmStats::refresh(JvmMetrics* jvm_metrics) const { jvm_metrics->jvm_gc_g1_old_generation_count->set_value(gcCollectionCount); jvm_metrics->jvm_gc_g1_old_generation_time_ms->set_value(gcCollectionTime); } - - env->ReleaseStringUTFChars((jstring)gcName, gcNameStr); } } return Status::OK(); } -JvmStats::~JvmStats() { - if (!_init_complete) { - return; - } - try { - JNIEnv* env = nullptr; - Status st = JniUtil::GetJNIEnv(&env); - if (!st.ok()) { - return; - } - env->DeleteGlobalRef(_managementFactoryClass); - env->DeleteGlobalRef(_memoryUsageClass); - env->DeleteGlobalRef(_memoryMXBeanClass); - env->DeleteGlobalRef(_listClass); - env->DeleteGlobalRef(_memoryPoolMXBeanClass); - env->DeleteGlobalRef(_threadMXBeanClass); - env->DeleteGlobalRef(_threadInfoClass); - env->DeleteGlobalRef(_threadStateClass); - env->DeleteGlobalRef(_garbageCollectorMXBeanClass); - - env->DeleteGlobalRef(_newThreadStateObj); - env->DeleteGlobalRef(_runnableThreadStateObj); - env->DeleteGlobalRef(_blockedThreadStateObj); - env->DeleteGlobalRef(_waitingThreadStateObj); - env->DeleteGlobalRef(_timedWaitingThreadStateObj); - env->DeleteGlobalRef(_terminatedThreadStateObj); - - } catch (...) { - // In order to exit more gracefully, we catch the exception here. - } -} +JvmStats::~JvmStats() {} } // namespace doris diff --git a/be/src/util/jvm_metrics.h b/be/src/util/jvm_metrics.h index a059c666c1f4d1..fc9b58fbc10e3b 100644 --- a/be/src/util/jvm_metrics.h +++ b/be/src/util/jvm_metrics.h @@ -27,27 +27,26 @@ class JvmMetrics; class JvmStats { private: - jclass _managementFactoryClass = nullptr; - jmethodID _getMemoryMXBeanMethod = nullptr; - jclass _memoryUsageClass = nullptr; - jclass _memoryMXBeanClass = nullptr; - jmethodID _getHeapMemoryUsageMethod = nullptr; - jmethodID _getNonHeapMemoryUsageMethod = nullptr; - jmethodID _getMemoryUsageUsedMethod = nullptr; - jmethodID _getMemoryUsageCommittedMethod = nullptr; - jmethodID _getMemoryUsageMaxMethod = nullptr; - - jmethodID _getMemoryPoolMXBeansMethod = nullptr; - - jclass _listClass = nullptr; - jmethodID _getListSizeMethod = nullptr; - jmethodID _getListUseIndexMethod = nullptr; - - jclass _memoryPoolMXBeanClass = nullptr; - jmethodID _getMemoryPoolMXBeanUsageMethod = nullptr; - - jmethodID _getMemoryPollMXBeanPeakMethod = nullptr; - jmethodID _getMemoryPollMXBeanNameMethod = nullptr; + Jni::GlobalClass _managementFactoryClass; + Jni::MethodId _getMemoryMXBeanMethod; + Jni::GlobalClass _memoryUsageClass; + Jni::GlobalClass _memoryMXBeanClass; + Jni::MethodId _getHeapMemoryUsageMethod; + Jni::MethodId _getNonHeapMemoryUsageMethod; + Jni::MethodId _getMemoryUsageUsedMethod; + Jni::MethodId _getMemoryUsageCommittedMethod; + Jni::MethodId _getMemoryUsageMaxMethod; + + Jni::MethodId _getMemoryPoolMXBeansMethod; + + Jni::GlobalClass _listClass; + Jni::MethodId _getListSizeMethod; + Jni::MethodId _getListUseIndexMethod; + + Jni::GlobalClass _memoryPoolMXBeanClass; + Jni::MethodId _getMemoryPoolMXBeanUsageMethod; + Jni::MethodId _getMemoryPoolMXBeanPeakMethod; + Jni::MethodId _getMemoryPoolMXBeanNameMethod; enum memoryPoolNameEnum { YOUNG, SURVIVOR, OLD }; const std::map _memoryPoolName = { @@ -68,34 +67,34 @@ class JvmStats { }; - jmethodID _getThreadMXBeanMethod = nullptr; - jclass _threadMXBeanClass = nullptr; - jmethodID _getAllThreadIdsMethod = nullptr; - jmethodID _getThreadInfoMethod = nullptr; - jclass _threadInfoClass = nullptr; + Jni::MethodId _getThreadMXBeanMethod; + Jni::GlobalClass _threadMXBeanClass; + Jni::MethodId _getAllThreadIdsMethod; + Jni::MethodId _getThreadInfoMethod; + Jni::GlobalClass _threadInfoClass; - jmethodID _getPeakThreadCountMethod = nullptr; + Jni::MethodId _getPeakThreadCountMethod; - jmethodID _getThreadStateMethod = nullptr; - jclass _threadStateClass = nullptr; + Jni::MethodId _getThreadStateMethod; + Jni::GlobalClass _threadStateClass; - jobject _newThreadStateObj = nullptr; - jobject _runnableThreadStateObj = nullptr; - jobject _blockedThreadStateObj = nullptr; - jobject _waitingThreadStateObj = nullptr; - jobject _timedWaitingThreadStateObj = nullptr; - jobject _terminatedThreadStateObj = nullptr; + Jni::GlobalObject _newThreadStateObj; + Jni::GlobalObject _runnableThreadStateObj; + Jni::GlobalObject _blockedThreadStateObj; + Jni::GlobalObject _waitingThreadStateObj; + Jni::GlobalObject _timedWaitingThreadStateObj; + Jni::GlobalObject _terminatedThreadStateObj; - jclass _garbageCollectorMXBeanClass = nullptr; - jmethodID _getGCNameMethod = nullptr; - jmethodID _getGarbageCollectorMXBeansMethod = nullptr; - jmethodID _getGCCollectionCountMethod = nullptr; - jmethodID _getGCCollectionTimeMethod = nullptr; + Jni::GlobalClass _garbageCollectorMXBeanClass; + Jni::MethodId _getGCNameMethod; + Jni::MethodId _getGarbageCollectorMXBeansMethod; + Jni::MethodId _getGCCollectionCountMethod; + Jni::MethodId _getGCCollectionTimeMethod; bool _init_complete = false; public: - Status init(JNIEnv* env); + Status init(); bool init_complete() const { return _init_complete; } void set_complete(bool val) { _init_complete = val; } Status refresh(JvmMetrics* jvm_metrics) const; @@ -104,7 +103,7 @@ class JvmStats { class JvmMetrics { public: - JvmMetrics(MetricRegistry* registry, JNIEnv* env); + JvmMetrics(MetricRegistry* registry); ~JvmMetrics(); void update(); diff --git a/be/src/vec/aggregate_functions/aggregate_function_java_udaf.h b/be/src/vec/aggregate_functions/aggregate_function_java_udaf.h index 2e63275cdad921..a8905126141b9d 100644 --- a/be/src/vec/aggregate_functions/aggregate_function_java_udaf.h +++ b/be/src/vec/aggregate_functions/aggregate_function_java_udaf.h @@ -65,19 +65,11 @@ struct AggregateJavaUdafData { Status close_and_delete_object() { JNIEnv* env = nullptr; - Defer defer {[&]() { - if (env != nullptr) { - env->DeleteGlobalRef(executor_cl); - env->DeleteGlobalRef(executor_obj); - } - }}; - Status st = JniUtil::GetJNIEnv(&env); - if (!st.ok()) { - LOG(WARNING) << "Failed to get JNIEnv"; - return st; - } - env->CallNonvirtualVoidMethod(executor_obj, executor_cl, executor_close_id); - st = JniUtil::GetJniExceptionMsg(env); + + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + auto st = executor_obj.call_nonvirtual_void_method(env, executor_cl, executor_close_id) + .call(); if (!st.ok()) { LOG(WARNING) << "Failed to close JAVA UDAF: " << st.to_string(); return st; @@ -87,32 +79,22 @@ struct AggregateJavaUdafData { Status init_udaf(const TFunction& fn, const std::string& local_location) { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf init_udaf function"); - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, UDAF_EXECUTOR_CLASS, &executor_cl)); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf init_udaf function"); + RETURN_IF_ERROR(Jni::Util::find_class(env, UDAF_EXECUTOR_CLASS, &executor_cl)); RETURN_NOT_OK_STATUS_WITH_WARN(register_func_id(env), "Java-Udaf register_func_id function"); - // Add a scoped cleanup jni reference object. This cleans up local refs made below. - JniLocalFrame jni_frame; - { - TJavaUdfExecutorCtorParams ctor_params; - ctor_params.__set_fn(fn); - if (!fn.hdfs_location.empty() && !fn.checksum.empty()) { - ctor_params.__set_location(local_location); - } - jbyteArray ctor_params_bytes; - - // Pushed frame will be popped when jni_frame goes out-of-scope. - RETURN_IF_ERROR(jni_frame.push(env)); - RETURN_IF_ERROR(SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); - executor_obj = env->NewObject(executor_cl, executor_ctor_id, ctor_params_bytes); - - jbyte* pBytes = env->GetByteArrayElements(ctor_params_bytes, nullptr); - env->ReleaseByteArrayElements(ctor_params_bytes, pBytes, JNI_ABORT); - env->DeleteLocalRef(ctor_params_bytes); + TJavaUdfExecutorCtorParams ctor_params; + ctor_params.__set_fn(fn); + if (!fn.hdfs_location.empty() && !fn.checksum.empty()) { + ctor_params.__set_location(local_location); } - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, executor_obj, &executor_obj)); + + Jni::LocalArray ctor_params_bytes; + RETURN_IF_ERROR(Jni::Util::SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); + RETURN_IF_ERROR(executor_cl.new_object(env, executor_ctor_id) + .with_arg(ctor_params_bytes) + .call(&executor_obj)); return Status::OK(); } @@ -120,7 +102,7 @@ struct AggregateJavaUdafData { int64_t row_num_start, int64_t row_num_end, const DataTypes& argument_types, int64_t place_offset) { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf add function"); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf add function"); Block input_block; for (size_t i = 0; i < argument_size; ++i) { @@ -134,70 +116,77 @@ struct AggregateJavaUdafData { {"meta_address", std::to_string((long)input_table.get())}, {"required_fields", input_table_schema.first}, {"columns_types", input_table_schema.second}}; - jobject input_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, input_params, &input_map)); + + Jni::LocalObject input_map; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, input_params, &input_map)); // invoke add batch // Keep consistent with the function signature of executor_add_batch_id. - env->CallObjectMethod(executor_obj, executor_add_batch_id, is_single_place, - cast_set(row_num_start), cast_set(row_num_end), - places_address, cast_set(place_offset), input_map); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(input_map); - return JniUtil::GetJniExceptionMsg(env); + + return executor_obj.call_void_method(env, executor_add_batch_id) + .with_arg((jboolean)is_single_place) + .with_arg(cast_set(row_num_start)) + .with_arg(cast_set(row_num_end)) + .with_arg(places_address) + .with_arg(cast_set(place_offset)) + .with_arg(input_map) + .call(); } Status merge(const AggregateJavaUdafData& rhs, int64_t place) { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf merge function"); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf merge function"); serialize_data = rhs.serialize_data; - jsize len = cast_set(serialize_data.length()); // jsize needs to be used. - jbyteArray arr = env->NewByteArray(len); - env->SetByteArrayRegion(arr, 0, len, reinterpret_cast(serialize_data.data())); - env->CallNonvirtualVoidMethod(executor_obj, executor_cl, executor_merge_id, place, arr); - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); - jbyte* pBytes = env->GetByteArrayElements(arr, nullptr); - env->ReleaseByteArrayElements(arr, pBytes, JNI_ABORT); - env->DeleteLocalRef(arr); - return JniUtil::GetJniExceptionMsg(env); + Jni::LocalArray byte_arr; + RETURN_IF_ERROR(Jni::Util::WriteBufferToByteArray(env, (jbyte*)serialize_data.data(), + cast_set(serialize_data.length()), + &byte_arr)); + + return executor_obj.call_nonvirtual_void_method(env, executor_cl, executor_merge_id) + .with_arg((jlong)place) + .with_arg(byte_arr) + .call(); } Status write(BufferWritable& buf, int64_t place) { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf write function"); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf write function"); // TODO: Here get a byte[] from FE serialize, and then allocate the same length bytes to // save it in BE, Because i'm not sure there is a way to use the byte[] not allocate again. - jbyteArray arr = (jbyteArray)(env->CallNonvirtualObjectMethod( - executor_obj, executor_cl, executor_serialize_id, place)); - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); - int len = env->GetArrayLength(arr); + Jni::LocalArray arr; + RETURN_IF_ERROR( + executor_obj.call_nonvirtual_object_method(env, executor_cl, executor_serialize_id) + .with_arg((jlong)place) + .call(&arr)); + + jsize len = 0; + RETURN_IF_ERROR(arr.get_length(env, &len)); serialize_data.resize(len); - env->GetByteArrayRegion(arr, 0, len, reinterpret_cast(serialize_data.data())); + RETURN_IF_ERROR(arr.get_byte_elements(env, 0, len, + reinterpret_cast(serialize_data.data()))); buf.write_binary(serialize_data); - jbyte* pBytes = env->GetByteArrayElements(arr, nullptr); - env->ReleaseByteArrayElements(arr, pBytes, JNI_ABORT); - env->DeleteLocalRef(arr); - return JniUtil::GetJniExceptionMsg(env); + return Status::OK(); } Status reset(int64_t place) { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf reset function"); - env->CallNonvirtualVoidMethod(executor_obj, executor_cl, executor_reset_id, place); - return JniUtil::GetJniExceptionMsg(env); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf reset function"); + return executor_obj.call_nonvirtual_void_method(env, executor_cl, executor_reset_id) + .with_arg(place) + .call(); } void read(BufferReadable& buf) { buf.read_binary(serialize_data); } Status destroy() { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf destroy function"); - env->CallNonvirtualVoidMethod(executor_obj, executor_cl, executor_destroy_id); - return JniUtil::GetJniExceptionMsg(env); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf destroy function"); + return executor_obj.call_nonvirtual_void_method(env, executor_cl, executor_destroy_id) + .call(); } Status get(IColumn& to, const DataTypePtr& result_type, int64_t place) const { JNIEnv* env = nullptr; - RETURN_NOT_OK_STATUS_WITH_WARN(JniUtil::GetJNIEnv(&env), "Java-Udaf get value function"); + RETURN_NOT_OK_STATUS_WITH_WARN(Jni::Env::Get(&env), "Java-Udaf get value function"); Block output_block; output_block.insert(ColumnWithTypeAndName(to.get_ptr(), result_type, "_result_")); @@ -206,58 +195,55 @@ struct AggregateJavaUdafData { std::map output_params = {{"is_nullable", output_nullable}, {"required_fields", output_table_schema.first}, {"columns_types", output_table_schema.second}}; - jobject output_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, output_params, &output_map)); - long output_address = - env->CallLongMethod(executor_obj, executor_get_value_id, place, output_map); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(output_map); - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); + + Jni::LocalObject output_map; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, output_params, &output_map)); + long output_address; + + RETURN_IF_ERROR(executor_obj.call_long_method(env, executor_get_value_id) + .with_arg(place) + .with_arg(output_map) + .call(&output_address)); + return JniConnector::fill_block(&output_block, {0}, output_address); } private: Status register_func_id(JNIEnv* env) { - auto register_id = [&](const char* func_name, const char* func_sign, jmethodID& func_id) { - func_id = env->GetMethodID(executor_cl, func_name, func_sign); - Status s = JniUtil::GetJniExceptionMsg(env); - if (!s.ok()) { - LOG(WARNING) << "Failed to register function " << func_name << ": " - << s.to_string(); - return Status::InternalError(absl::Substitute( - "Java-Udaf register_func_id meet error and error is $0", s.to_string())); - } - return s; - }; - RETURN_IF_ERROR(register_id("", UDAF_EXECUTOR_CTOR_SIGNATURE, executor_ctor_id)); - RETURN_IF_ERROR(register_id("reset", UDAF_EXECUTOR_RESET_SIGNATURE, executor_reset_id)); - RETURN_IF_ERROR(register_id("close", UDAF_EXECUTOR_CLOSE_SIGNATURE, executor_close_id)); - RETURN_IF_ERROR(register_id("merge", UDAF_EXECUTOR_MERGE_SIGNATURE, executor_merge_id)); - RETURN_IF_ERROR( - register_id("serialize", UDAF_EXECUTOR_SERIALIZE_SIGNATURE, executor_serialize_id)); - RETURN_IF_ERROR( - register_id("getValue", UDAF_EXECUTOR_GET_SIGNATURE, executor_get_value_id)); - RETURN_IF_ERROR( - register_id("destroy", UDAF_EXECUTOR_DESTROY_SIGNATURE, executor_destroy_id)); - RETURN_IF_ERROR( - register_id("addBatch", UDAF_EXECUTOR_ADD_SIGNATURE, executor_add_batch_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "", UDAF_EXECUTOR_CTOR_SIGNATURE, + &executor_ctor_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "reset", UDAF_EXECUTOR_RESET_SIGNATURE, + &executor_reset_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "close", UDAF_EXECUTOR_CLOSE_SIGNATURE, + &executor_close_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "merge", UDAF_EXECUTOR_MERGE_SIGNATURE, + &executor_merge_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "serialize", UDAF_EXECUTOR_SERIALIZE_SIGNATURE, + &executor_serialize_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "getValue", UDAF_EXECUTOR_GET_SIGNATURE, + &executor_get_value_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "destroy", UDAF_EXECUTOR_DESTROY_SIGNATURE, + &executor_destroy_id)); + RETURN_IF_ERROR(executor_cl.get_method(env, "addBatch", UDAF_EXECUTOR_ADD_SIGNATURE, + &executor_add_batch_id)); + return Status::OK(); } private: // TODO: too many variables are hold, it's causing a lot of memory waste // it's time to refactor it. - jclass executor_cl; - jobject executor_obj; - jmethodID executor_ctor_id; - - jmethodID executor_add_batch_id; - jmethodID executor_merge_id; - jmethodID executor_serialize_id; - jmethodID executor_get_value_id; - jmethodID executor_reset_id; - jmethodID executor_close_id; - jmethodID executor_destroy_id; + Jni::GlobalClass executor_cl; + Jni::GlobalObject executor_obj; + + Jni::MethodId executor_ctor_id; + Jni::MethodId executor_add_batch_id; + Jni::MethodId executor_merge_id; + Jni::MethodId executor_serialize_id; + Jni::MethodId executor_get_value_id; + Jni::MethodId executor_reset_id; + Jni::MethodId executor_close_id; + Jni::MethodId executor_destroy_id; int argument_size = 0; std::string serialize_data; }; diff --git a/be/src/vec/exec/format/table/trino_connector_jni_reader.cpp b/be/src/vec/exec/format/table/trino_connector_jni_reader.cpp index b2b21fda33f352..1119de5065bfa7 100644 --- a/be/src/vec/exec/format/table/trino_connector_jni_reader.cpp +++ b/be/src/vec/exec/format/table/trino_connector_jni_reader.cpp @@ -84,36 +84,25 @@ Status TrinoConnectorJniReader::init_reader() { Status TrinoConnectorJniReader::_set_spi_plugins_dir() { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + // get PluginLoader class - jclass plugin_loader_cls; + Jni::LocalClass plugin_loader_cls; std::string plugin_loader_str = "org/apache/doris/trinoconnector/TrinoConnectorPluginLoader"; RETURN_IF_ERROR( - JniUtil::get_jni_scanner_class(env, plugin_loader_str.c_str(), &plugin_loader_cls)); - if (!plugin_loader_cls) { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::InternalError("Fail to get JniScanner class."); - } - RETURN_ERROR_IF_EXC(env); + Jni::Util::get_jni_scanner_class(env, plugin_loader_str.c_str(), &plugin_loader_cls)); - // get method: setPluginsDir(String pluginsDir) - jmethodID set_plugins_dir_method = - env->GetStaticMethodID(plugin_loader_cls, "setPluginsDir", "(Ljava/lang/String;)V"); - RETURN_ERROR_IF_EXC(env); + Jni::MethodId set_plugins_dir_method; + RETURN_IF_ERROR(plugin_loader_cls.get_static_method( + env, "setPluginsDir", "(Ljava/lang/String;)V", &set_plugins_dir_method)); - // call: setPluginsDir(String pluginsDir) - jstring trino_connector_plugin_path = - env->NewStringUTF(doris::config::trino_connector_plugin_dir.c_str()); - RETURN_ERROR_IF_EXC(env); - env->CallStaticVoidMethod(plugin_loader_cls, set_plugins_dir_method, - trino_connector_plugin_path); - RETURN_ERROR_IF_EXC(env); - env->DeleteLocalRef(trino_connector_plugin_path); - RETURN_ERROR_IF_EXC(env); + Jni::LocalString trino_connector_plugin_path; + RETURN_IF_ERROR(Jni::LocalString::new_string( + env, doris::config::trino_connector_plugin_dir.c_str(), &trino_connector_plugin_path)); - return Status::OK(); + return plugin_loader_cls.call_static_void_method(env, set_plugins_dir_method) + .with_arg(trino_connector_plugin_path) + .call(); } #include "common/compile_check_end.h" diff --git a/be/src/vec/exec/jni_connector.cpp b/be/src/vec/exec/jni_connector.cpp index 30576913a78f6a..57210b4e17d6d3 100644 --- a/be/src/vec/exec/jni_connector.cpp +++ b/be/src/vec/exec/jni_connector.cpp @@ -92,12 +92,13 @@ Status JniConnector::open(RuntimeState* state, RuntimeProfile* profile) { if (!_is_table_schema) { batch_size = _state->batch_size(); } - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); SCOPED_RAW_TIMER(&_jni_scanner_open_watcher); _scanner_params.emplace("time_zone", _state->timezone()); RETURN_IF_ERROR(_init_jni_scanner(env, batch_size)); // Call org.apache.doris.common.jni.JniScanner#open - env->CallVoidMethod(_jni_scanner_obj, _jni_scanner_open); + RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _jni_scanner_open).call()); + RETURN_ERROR_IF_EXC(env); _scanner_opened = true; return Status::OK(); @@ -111,13 +112,13 @@ Status JniConnector::get_next_block(Block* block, size_t* read_rows, bool* eof) // Call org.apache.doris.common.jni.JniScanner#getNextBatchMeta // return the address of meta information JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); long meta_address = 0; { SCOPED_RAW_TIMER(&_java_scan_watcher); - meta_address = env->CallLongMethod(_jni_scanner_obj, _jni_scanner_get_next_batch); + RETURN_IF_ERROR(_jni_scanner_obj.call_long_method(env, _jni_scanner_get_next_batch) + .call(&meta_address)); } - RETURN_ERROR_IF_EXC(env); if (meta_address == 0) { // Address == 0 when there's no data in scanner *read_rows = 0; @@ -134,64 +135,56 @@ Status JniConnector::get_next_block(Block* block, size_t* read_rows, bool* eof) RETURN_IF_ERROR(_fill_block(block, num_rows)); *read_rows = num_rows; *eof = false; - env->CallVoidMethod(_jni_scanner_obj, _jni_scanner_release_table); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _jni_scanner_release_table).call()); _has_read += num_rows; return Status::OK(); } Status JniConnector::get_table_schema(std::string& table_schema_str) { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); - jstring jstr = (jstring)env->CallObjectMethod(_jni_scanner_obj, _jni_scanner_get_table_schema); - RETURN_ERROR_IF_EXC(env); - - const char* cstr = env->GetStringUTFChars(jstr, nullptr); - RETURN_ERROR_IF_EXC(env); - - if (cstr == nullptr) { - return Status::RuntimeError("GetStringUTFChars returned null"); - } - - table_schema_str = std::string(cstr); // copy to std::string - env->ReleaseStringUTFChars(jstr, cstr); - env->DeleteLocalRef(jstr); + Jni::LocalString jstr; + RETURN_IF_ERROR( + _jni_scanner_obj.call_object_method(env, _jni_scanner_get_table_schema).call(&jstr)); + Jni::LocalStringBufferGuard cstr; + RETURN_IF_ERROR(jstr.get_string_chars(env, &cstr)); + table_schema_str = std::string {cstr.get()}; // copy to std::string return Status::OK(); } Status JniConnector::get_statistics(JNIEnv* env, std::map* result) { result->clear(); - jobject metrics = env->CallObjectMethod(_jni_scanner_obj, _jni_scanner_get_statistics); - jthrowable exc = (env)->ExceptionOccurred(); - if (exc != nullptr) { - LOG(WARNING) << "get_statistics has error: " - << JniUtil::GetJniExceptionMsg(env).to_string(); - env->DeleteLocalRef(metrics); - return Status::OK(); - } - RETURN_IF_ERROR(JniUtil::convert_to_cpp_map(env, metrics, result)); - env->DeleteLocalRef(metrics); + Jni::LocalObject metrics; + RETURN_IF_ERROR( + _jni_scanner_obj.call_object_method(env, _jni_scanner_get_statistics).call(&metrics)); + + RETURN_IF_ERROR(Jni::Util::convert_to_cpp_map(env, metrics, result)); return Status::OK(); } Status JniConnector::close() { if (!_closed) { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - if (_scanner_opened && _jni_scanner_obj != nullptr) { + RETURN_IF_ERROR(Jni::Env::Get(&env)); + if (_scanner_opened) { COUNTER_UPDATE(_open_scanner_time, _jni_scanner_open_watcher); COUNTER_UPDATE(_fill_block_time, _fill_block_watcher); RETURN_ERROR_IF_EXC(env); - int64_t _append = (int64_t)env->CallLongMethod(_jni_scanner_obj, - _jni_scanner_get_append_data_time); - RETURN_ERROR_IF_EXC(env); + int64_t _append = 0; + RETURN_IF_ERROR( + _jni_scanner_obj.call_long_method(env, _jni_scanner_get_append_data_time) + .call(&_append)); + COUNTER_UPDATE(_java_append_data_time, _append); - int64_t _create = (int64_t)env->CallLongMethod( - _jni_scanner_obj, _jni_scanner_get_create_vector_table_time); - RETURN_ERROR_IF_EXC(env); + int64_t _create = 0; + RETURN_IF_ERROR( + _jni_scanner_obj + .call_long_method(env, _jni_scanner_get_create_vector_table_time) + .call(&_create)); + COUNTER_UPDATE(_java_create_vector_table_time, _create); COUNTER_UPDATE(_java_scan_time, _java_scan_watcher - _append - _create); @@ -202,23 +195,9 @@ Status JniConnector::close() { // _fill_block may be failed and returned, we should release table in close. // org.apache.doris.common.jni.JniScanner#releaseTable is idempotent - env->CallVoidMethod(_jni_scanner_obj, _jni_scanner_release_table); - RETURN_ERROR_IF_EXC(env); - env->CallVoidMethod(_jni_scanner_obj, _jni_scanner_close); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(_jni_scanner_obj); - RETURN_ERROR_IF_EXC(env); - } - if (_jni_scanner_cls != nullptr) { - // _jni_scanner_cls may be null if init connector failed - env->DeleteGlobalRef(_jni_scanner_cls); - } - _closed = true; - jthrowable exc = (env)->ExceptionOccurred(); - if (exc != nullptr) { - // Ensure successful resource release - throw Exception(Status::FatalError("Failed to release jni resource: {}", - JniUtil::GetJniExceptionMsg(env).to_string())); + RETURN_IF_ERROR( + _jni_scanner_obj.call_void_method(env, _jni_scanner_release_table).call()); + RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _jni_scanner_close).call()); } } return Status::OK(); @@ -226,53 +205,36 @@ Status JniConnector::close() { Status JniConnector::_init_jni_scanner(JNIEnv* env, int batch_size) { RETURN_IF_ERROR( - JniUtil::get_jni_scanner_class(env, _connector_class.c_str(), &_jni_scanner_cls)); - if (_jni_scanner_cls == nullptr) [[unlikely]] { - if (env->ExceptionOccurred()) { - env->ExceptionDescribe(); - } - return Status::InternalError("Fail to get JniScanner class."); - } - RETURN_ERROR_IF_EXC(env); + Jni::Util::get_jni_scanner_class(env, _connector_class.c_str(), &_jni_scanner_cls)); - jmethodID scanner_constructor = - env->GetMethodID(_jni_scanner_cls, "", "(ILjava/util/Map;)V"); - RETURN_ERROR_IF_EXC(env); - - // prepare constructor parameters - jobject hashmap_object; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, _scanner_params, &hashmap_object)); - jobject jni_scanner_obj = - env->NewObject(_jni_scanner_cls, scanner_constructor, batch_size, hashmap_object); - - RETURN_ERROR_IF_EXC(env); + Jni::MethodId scanner_constructor; + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "", "(ILjava/util/Map;)V", + &scanner_constructor)); // prepare constructor parameters - env->DeleteGlobalRef(hashmap_object); - RETURN_ERROR_IF_EXC(env); - - _jni_scanner_open = env->GetMethodID(_jni_scanner_cls, "open", "()V"); - RETURN_ERROR_IF_EXC(env); - _jni_scanner_get_next_batch = env->GetMethodID(_jni_scanner_cls, "getNextBatchMeta", "()J"); - RETURN_ERROR_IF_EXC(env); - _jni_scanner_get_append_data_time = - env->GetMethodID(_jni_scanner_cls, "getAppendDataTime", "()J"); - RETURN_ERROR_IF_EXC(env); - _jni_scanner_get_create_vector_table_time = - env->GetMethodID(_jni_scanner_cls, "getCreateVectorTableTime", "()J"); - RETURN_ERROR_IF_EXC(env); - _jni_scanner_get_table_schema = - env->GetMethodID(_jni_scanner_cls, "getTableSchema", "()Ljava/lang/String;"); - RETURN_ERROR_IF_EXC(env); - _jni_scanner_close = env->GetMethodID(_jni_scanner_cls, "close", "()V"); - _jni_scanner_release_column = env->GetMethodID(_jni_scanner_cls, "releaseColumn", "(I)V"); - _jni_scanner_release_table = env->GetMethodID(_jni_scanner_cls, "releaseTable", "()V"); - _jni_scanner_get_statistics = - env->GetMethodID(_jni_scanner_cls, "getStatistics", "()Ljava/util/Map;"); - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, jni_scanner_obj, &_jni_scanner_obj)); - env->DeleteLocalRef(jni_scanner_obj); - RETURN_ERROR_IF_EXC(env); + Jni::LocalObject hashmap_object; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, _scanner_params, &hashmap_object)); + RETURN_IF_ERROR(_jni_scanner_cls.new_object(env, scanner_constructor) + .with_arg(batch_size) + .with_arg(hashmap_object) + .call(&_jni_scanner_obj)); + + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "open", "()V", &_jni_scanner_open)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "getNextBatchMeta", "()J", + &_jni_scanner_get_next_batch)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "getAppendDataTime", "()J", + &_jni_scanner_get_append_data_time)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "getCreateVectorTableTime", "()J", + &_jni_scanner_get_create_vector_table_time)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "getTableSchema", "()Ljava/lang/String;", + &_jni_scanner_get_table_schema)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "close", "()V", &_jni_scanner_close)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "releaseColumn", "(I)V", + &_jni_scanner_release_column)); + RETURN_IF_ERROR( + _jni_scanner_cls.get_method(env, "releaseTable", "()V", &_jni_scanner_release_table)); + RETURN_IF_ERROR(_jni_scanner_cls.get_method(env, "getStatistics", "()Ljava/util/Map;", + &_jni_scanner_get_statistics)); return Status::OK(); } @@ -314,7 +276,7 @@ Status JniConnector::fill_block(Block* block, const ColumnNumbers& arguments, lo Status JniConnector::_fill_block(Block* block, size_t num_rows) { SCOPED_RAW_TIMER(&_fill_block_watcher); JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); for (int i = 0; i < _column_names.size(); ++i) { auto& column_with_type_and_name = block->get_by_position(_col_name_to_block_idx->at(_column_names[i])); @@ -322,7 +284,9 @@ Status JniConnector::_fill_block(Block* block, size_t num_rows) { auto& column_type = column_with_type_and_name.type; RETURN_IF_ERROR(_fill_column(_table_meta, column_ptr, column_type, num_rows)); // Column is not released when _fill_column failed. It will be released when releasing table. - env->CallVoidMethod(_jni_scanner_obj, _jni_scanner_release_column, i); + RETURN_IF_ERROR(_jni_scanner_obj.call_void_method(env, _jni_scanner_release_column) + .with_arg(i) + .call()); RETURN_ERROR_IF_EXC(env); } return Status::OK(); @@ -846,7 +810,7 @@ std::pair JniConnector::parse_table_schema(Block* bloc void JniConnector::_collect_profile_before_close() { if (_scanner_opened && _profile != nullptr) { JNIEnv* env = nullptr; - Status st = JniUtil::GetJNIEnv(&env); + Status st = Jni::Env::Get(&env); if (!st) { LOG(WARNING) << "failed to get jni env when collect profile: " << st; return; diff --git a/be/src/vec/exec/jni_connector.h b/be/src/vec/exec/jni_connector.h index 12ba6f7b8f64d6..08ce188689c2f4 100644 --- a/be/src/vec/exec/jni_connector.h +++ b/be/src/vec/exec/jni_connector.h @@ -33,6 +33,7 @@ #include "runtime/define_primitive_type.h" #include "runtime/primitive_type.h" #include "runtime/types.h" +#include "util/jni-util.h" #include "util/profile_collector.h" #include "util/runtime_profile.h" #include "util/string_util.h" @@ -309,17 +310,18 @@ class JniConnector : public ProfileCollector { bool _closed = false; bool _scanner_opened = false; - jclass _jni_scanner_cls = nullptr; - jobject _jni_scanner_obj = nullptr; - jmethodID _jni_scanner_open = nullptr; - jmethodID _jni_scanner_get_append_data_time = nullptr; - jmethodID _jni_scanner_get_create_vector_table_time = nullptr; - jmethodID _jni_scanner_get_next_batch = nullptr; - jmethodID _jni_scanner_get_table_schema = nullptr; - jmethodID _jni_scanner_close = nullptr; - jmethodID _jni_scanner_release_column = nullptr; - jmethodID _jni_scanner_release_table = nullptr; - jmethodID _jni_scanner_get_statistics = nullptr; + + Jni::GlobalClass _jni_scanner_cls; + Jni::GlobalObject _jni_scanner_obj; + Jni::MethodId _jni_scanner_open; + Jni::MethodId _jni_scanner_get_append_data_time; + Jni::MethodId _jni_scanner_get_create_vector_table_time; + Jni::MethodId _jni_scanner_get_next_batch; + Jni::MethodId _jni_scanner_get_table_schema; + Jni::MethodId _jni_scanner_close; + Jni::MethodId _jni_scanner_release_column; + Jni::MethodId _jni_scanner_release_table; + Jni::MethodId _jni_scanner_get_statistics; TableMetaAddress _table_meta; diff --git a/be/src/vec/exec/vjdbc_connector.cpp b/be/src/vec/exec/vjdbc_connector.cpp index 9924df7b3607a1..38a83a01812210 100644 --- a/be/src/vec/exec/vjdbc_connector.cpp +++ b/be/src/vec/exec/vjdbc_connector.cpp @@ -78,14 +78,6 @@ Status JdbcConnector::close(Status /*unused*/) { return Status::OK(); } - JNIEnv* env = nullptr; - Status status = JniUtil::GetJNIEnv(&env); - if (!status.ok() || env == nullptr) { - LOG(WARNING) << "Failed to get JNIEnv in close(): " << status.to_string(); - _closed = true; - return status; - } - // Try to abort transaction and call Java close(), but don't block cleanup if (_is_in_transaction) { Status abort_status = abort_trans(); @@ -94,26 +86,11 @@ Status JdbcConnector::close(Status /*unused*/) { } } - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_close_id); - if (env->ExceptionCheck()) { - LOG(WARNING) << "Java close() failed: " << JniUtil::GetJniExceptionMsg(env).to_string(); - env->ExceptionClear(); - } - - // Always delete Global References to allow Java GC - if (_executor_factory_clazz != nullptr) { - env->DeleteGlobalRef(_executor_factory_clazz); - _executor_factory_clazz = nullptr; - } - if (_executor_clazz != nullptr) { - env->DeleteGlobalRef(_executor_clazz); - _executor_clazz = nullptr; - } - if (_executor_obj != nullptr) { - env->DeleteGlobalRef(_executor_obj); - _executor_obj = nullptr; - } - + JNIEnv* env = nullptr; + RETURN_IF_ERROR(Jni::Env::Get(&env)); + RETURN_IF_ERROR( + _executor_obj.call_nonvirtual_void_method(env, _executor_clazz, _executor_close_id) + .call()); _closed = true; return Status::OK(); } @@ -125,80 +102,70 @@ Status JdbcConnector::open(RuntimeState* state, bool read) { } JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - RETURN_IF_ERROR(JniUtil::get_jni_scanner_class(env, JDBC_EXECUTOR_FACTORY_CLASS, - &_executor_factory_clazz)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _executor_factory_ctor_id, env, - GetStaticMethodID(_executor_factory_clazz, "getExecutorClass", - "(Lorg/apache/doris/thrift/TOdbcTableType;)Ljava/lang/String;")); + RETURN_IF_ERROR(Jni::Util::get_jni_scanner_class(env, JDBC_EXECUTOR_FACTORY_CLASS, + &_executor_factory_clazz)); - jobject jtable_type = nullptr; - RETURN_IF_ERROR(_get_java_table_type(env, _conn_param.table_type, &jtable_type)); + RETURN_IF_ERROR(_executor_factory_clazz.get_static_method( + env, "getExecutorClass", "(Lorg/apache/doris/thrift/TOdbcTableType;)Ljava/lang/String;", + &_executor_factory_ctor_id)); - JNI_CALL_METHOD_CHECK_EXCEPTION_DELETE_REF( - jobject, executor_name, env, - CallStaticObjectMethod(_executor_factory_clazz, _executor_factory_ctor_id, - jtable_type)); + Jni::LocalObject jtable_type; + RETURN_IF_ERROR(_get_java_table_type(env, _conn_param.table_type, &jtable_type)); - const char* executor_name_str = env->GetStringUTFChars((jstring)executor_name, nullptr); + Jni::LocalString executor_name; + RETURN_IF_ERROR( + _executor_factory_clazz.call_static_object_method(env, _executor_factory_ctor_id) + .with_arg(jtable_type) + .call(&executor_name)); - RETURN_IF_ERROR(JniUtil::get_jni_scanner_class(env, executor_name_str, &_executor_clazz)); + Jni::LocalStringBufferGuard executor_name_str; + RETURN_IF_ERROR(executor_name.get_string_chars(env, &executor_name_str)); - env->DeleteGlobalRef(jtable_type); - RETURN_ERROR_IF_EXC(env); - env->ReleaseStringUTFChars((jstring)executor_name, executor_name_str); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR( + Jni::Util::get_jni_scanner_class(env, executor_name_str.get(), &_executor_clazz)); #undef GET_BASIC_JAVA_CLAZZ RETURN_IF_ERROR(_register_func_id(env)); - // Add a scoped cleanup jni reference object. This cleans up local refs made below. - JniLocalFrame jni_frame; + std::string driver_path; + RETURN_IF_ERROR(_get_real_url(_conn_param.driver_path, &driver_path)); + + TJdbcExecutorCtorParams ctor_params; + ctor_params.__set_statement(_sql_str); + ctor_params.__set_catalog_id(_conn_param.catalog_id); + ctor_params.__set_jdbc_url(_conn_param.jdbc_url); + ctor_params.__set_jdbc_user(_conn_param.user); + ctor_params.__set_jdbc_password(_conn_param.passwd); + ctor_params.__set_jdbc_driver_class(_conn_param.driver_class); + ctor_params.__set_driver_path(driver_path); + ctor_params.__set_jdbc_driver_checksum(_conn_param.driver_checksum); + if (state == nullptr) { + ctor_params.__set_batch_size(read ? 1 : 0); + } else { + ctor_params.__set_batch_size(read ? state->batch_size() : 0); + } + ctor_params.__set_op(read ? TJdbcOperation::READ : TJdbcOperation::WRITE); + ctor_params.__set_table_type(_conn_param.table_type); + ctor_params.__set_connection_pool_min_size(_conn_param.connection_pool_min_size); + ctor_params.__set_connection_pool_max_size(_conn_param.connection_pool_max_size); + ctor_params.__set_connection_pool_max_wait_time(_conn_param.connection_pool_max_wait_time); + ctor_params.__set_connection_pool_max_life_time(_conn_param.connection_pool_max_life_time); + ctor_params.__set_connection_pool_cache_clear_time( + config::jdbc_connection_pool_cache_clear_time_sec); + ctor_params.__set_connection_pool_keep_alive(_conn_param.connection_pool_keep_alive); + ctor_params.__set_is_tvf(_conn_param.is_tvf); + + Jni::LocalArray ctor_params_bytes; + RETURN_IF_ERROR(Jni::Util::SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); + { - std::string driver_path; - RETURN_IF_ERROR(_get_real_url(_conn_param.driver_path, &driver_path)); - - TJdbcExecutorCtorParams ctor_params; - ctor_params.__set_statement(_sql_str); - ctor_params.__set_catalog_id(_conn_param.catalog_id); - ctor_params.__set_jdbc_url(_conn_param.jdbc_url); - ctor_params.__set_jdbc_user(_conn_param.user); - ctor_params.__set_jdbc_password(_conn_param.passwd); - ctor_params.__set_jdbc_driver_class(_conn_param.driver_class); - ctor_params.__set_driver_path(driver_path); - ctor_params.__set_jdbc_driver_checksum(_conn_param.driver_checksum); - if (state == nullptr) { - ctor_params.__set_batch_size(read ? 1 : 0); - } else { - ctor_params.__set_batch_size(read ? state->batch_size() : 0); - } - ctor_params.__set_op(read ? TJdbcOperation::READ : TJdbcOperation::WRITE); - ctor_params.__set_table_type(_conn_param.table_type); - ctor_params.__set_connection_pool_min_size(_conn_param.connection_pool_min_size); - ctor_params.__set_connection_pool_max_size(_conn_param.connection_pool_max_size); - ctor_params.__set_connection_pool_max_wait_time(_conn_param.connection_pool_max_wait_time); - ctor_params.__set_connection_pool_max_life_time(_conn_param.connection_pool_max_life_time); - ctor_params.__set_connection_pool_cache_clear_time( - config::jdbc_connection_pool_cache_clear_time_sec); - ctor_params.__set_connection_pool_keep_alive(_conn_param.connection_pool_keep_alive); - ctor_params.__set_is_tvf(_conn_param.is_tvf); - - jbyteArray ctor_params_bytes; - // Pushed frame will be popped when jni_frame goes out-of-scope. - RETURN_IF_ERROR(jni_frame.push(env)); - RETURN_IF_ERROR(SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); - { - SCOPED_RAW_TIMER(&_jdbc_statistic._init_connector_timer); - _executor_obj = env->NewObject(_executor_clazz, _executor_ctor_id, ctor_params_bytes); - } - jbyte* pBytes = env->GetByteArrayElements(ctor_params_bytes, nullptr); - env->ReleaseByteArrayElements(ctor_params_bytes, pBytes, JNI_ABORT); - env->DeleteLocalRef(ctor_params_bytes); + SCOPED_RAW_TIMER(&_jdbc_statistic._init_connector_timer); + RETURN_IF_ERROR(_executor_clazz.new_object(env, _executor_ctor_id) + .with_arg(ctor_params_bytes) + .call(&_executor_obj)); } - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, _executor_obj, &_executor_obj)); _is_open = true; RETURN_IF_ERROR(begin_trans()); @@ -209,11 +176,11 @@ Status JdbcConnector::test_connection() { RETURN_IF_ERROR(open(nullptr, true)); JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_test_connection_id); - RETURN_ERROR_IF_EXC(env); - return Status::OK(); + return _executor_obj + .call_nonvirtual_void_method(env, _executor_clazz, _executor_test_connection_id) + .call(); } Status JdbcConnector::clean_datasource() { @@ -221,10 +188,11 @@ Status JdbcConnector::clean_datasource() { return Status::OK(); } JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_clean_datasource_id); - RETURN_ERROR_IF_EXC(env); - return Status::OK(); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + return _executor_obj + .call_nonvirtual_void_method(env, _executor_clazz, _executor_clean_datasource_id) + .call(); } Status JdbcConnector::query() { @@ -235,14 +203,16 @@ Status JdbcConnector::query() { auto materialize_num = _tuple_desc->slots().size(); JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); { SCOPED_RAW_TIMER(&_jdbc_statistic._execte_read_timer); - jint colunm_count = - env->CallNonvirtualIntMethod(_executor_obj, _executor_clazz, _executor_read_id); - if (auto status = JniUtil::GetJniExceptionMsg(env); !status) { + + jint colunm_count = 0; + auto st = _executor_obj.call_nonvirtual_int_method(env, _executor_clazz, _executor_read_id) + .call(&colunm_count); + if (!st.ok()) { return Status::InternalError("GetJniExceptionMsg meet error, query={}, msg={}", - _conn_param.query_string, status.to_string()); + _conn_param.query_string, st.to_string()); } if (colunm_count < materialize_num) { return Status::InternalError( @@ -265,15 +235,17 @@ Status JdbcConnector::get_next(bool* eos, Block* block, int batch_size) { JNIEnv* env = nullptr; { SCOPED_RAW_TIMER(&_jdbc_statistic._jni_setup_timer); // Timer for setting up JNI environment - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); } // _jni_setup_timer stops when going out of this scope jboolean has_next = JNI_FALSE; { SCOPED_RAW_TIMER(&_jdbc_statistic._has_next_timer); // Timer for hasNext check - has_next = env->CallNonvirtualBooleanMethod(_executor_obj, _executor_clazz, - _executor_has_next_id); - RETURN_ERROR_IF_EXC(env); + + RETURN_IF_ERROR( + _executor_obj + .call_nonvirtual_boolean_method(env, _executor_clazz, _executor_has_next_id) + .call(&has_next)); } // _has_next_timer stops here if (has_next != JNI_TRUE) { @@ -281,12 +253,10 @@ Status JdbcConnector::get_next(bool* eos, Block* block, int batch_size) { return Status::OK(); } - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); - auto column_size = _tuple_desc->slots().size(); auto slots = _tuple_desc->slots(); - jobject map = nullptr; + Jni::LocalObject map; { SCOPED_RAW_TIMER(&_jdbc_statistic._prepare_params_timer); // Timer for preparing params RETURN_IF_ERROR(_get_reader_params(block, env, column_size, &map)); @@ -297,14 +267,12 @@ Status JdbcConnector::get_next(bool* eos, Block* block, int batch_size) { SCOPED_RAW_TIMER( &_jdbc_statistic ._read_and_fill_vector_table_timer); // Timer for getBlockAddress call - address = - env->CallLongMethod(_executor_obj, _executor_get_block_address_id, batch_size, map); + RETURN_IF_ERROR(_executor_obj.call_long_method(env, _executor_get_block_address_id) + .with_arg(batch_size) + .with_arg(map) + .call(&address)); } // _get_block_address_timer stops here - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); - env->DeleteGlobalRef(map); - RETURN_ERROR_IF_EXC(env); - std::vector all_columns; for (uint32_t i = 0; i < column_size; ++i) { all_columns.push_back(i); @@ -326,7 +294,7 @@ Status JdbcConnector::get_next(bool* eos, Block* block, int batch_size) { cast_status = _cast_string_to_special(block, env, column_size); } // _cast_timer stops here - return JniUtil::GetJniExceptionMsg(env); + return Status::OK(); } Status JdbcConnector::append(vectorized::Block* block, @@ -342,7 +310,7 @@ Status JdbcConnector::exec_stmt_write(Block* block, const VExprContextSPtrs& out uint32_t* num_rows_sent) { SCOPED_TIMER(_result_send_timer); JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); // prepare table meta information std::unique_ptr meta_data; @@ -354,13 +322,14 @@ Status JdbcConnector::exec_stmt_write(Block* block, const VExprContextSPtrs& out std::map write_params = {{"meta_address", std::to_string(meta_address)}, {"required_fields", table_schema.first}, {"columns_types", table_schema.second}}; - jobject hashmap_object = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, write_params, &hashmap_object)); + Jni::LocalObject hashmap_object; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, write_params, &hashmap_object)); + + RETURN_IF_ERROR( + _executor_obj.call_nonvirtual_int_method(env, _executor_clazz, _executor_stmt_write_id) + .with_arg(hashmap_object) + .call()); - env->CallNonvirtualIntMethod(_executor_obj, _executor_clazz, _executor_stmt_write_id, - hashmap_object); - env->DeleteGlobalRef(hashmap_object); - RETURN_ERROR_IF_EXC(env); *num_rows_sent = static_cast(block->rows()); return Status::OK(); } @@ -368,9 +337,12 @@ Status JdbcConnector::exec_stmt_write(Block* block, const VExprContextSPtrs& out Status JdbcConnector::begin_trans() { if (_use_tranaction) { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_begin_trans_id); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + RETURN_IF_ERROR( + _executor_obj + .call_nonvirtual_void_method(env, _executor_clazz, _executor_begin_trans_id) + .call()); _is_in_transaction = true; } return Status::OK(); @@ -381,64 +353,61 @@ Status JdbcConnector::abort_trans() { return Status::InternalError("Abort transaction before begin trans."); } JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_abort_trans_id); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + RETURN_IF_ERROR( + _executor_obj + .call_nonvirtual_void_method(env, _executor_clazz, _executor_abort_trans_id) + .call()); return Status::OK(); } Status JdbcConnector::finish_trans() { if (_use_tranaction && _is_in_transaction) { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); - env->CallNonvirtualVoidMethod(_executor_obj, _executor_clazz, _executor_finish_trans_id); - RETURN_ERROR_IF_EXC(env); + RETURN_IF_ERROR(Jni::Env::Get(&env)); + + RETURN_IF_ERROR(_executor_obj + .call_nonvirtual_void_method(env, _executor_clazz, + _executor_finish_trans_id) + .call()); + _is_in_transaction = false; } return Status::OK(); } Status JdbcConnector::_register_func_id(JNIEnv* env) { - auto register_id = [&](jclass clazz, const char* func_name, const char* func_sign, - jmethodID& func_id) { - func_id = env->GetMethodID(clazz, func_name, func_sign); - Status s = JniUtil::GetJniExceptionMsg(env); - if (!s.ok()) { - return Status::InternalError(absl::Substitute( - "Jdbc connector _register_func_id meet error and error is $0", s.to_string())); - } - return s; - }; - - RETURN_IF_ERROR(register_id(_executor_clazz, "", JDBC_EXECUTOR_CTOR_SIGNATURE, - _executor_ctor_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "write", JDBC_EXECUTOR_STMT_WRITE_SIGNATURE, - _executor_stmt_write_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "read", "()I", _executor_read_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "close", JDBC_EXECUTOR_CLOSE_SIGNATURE, - _executor_close_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "hasNext", JDBC_EXECUTOR_HAS_NEXT_SIGNATURE, - _executor_has_next_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "getBlockAddress", "(ILjava/util/Map;)J", - _executor_get_block_address_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "", JDBC_EXECUTOR_CTOR_SIGNATURE, + &_executor_ctor_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "write", JDBC_EXECUTOR_STMT_WRITE_SIGNATURE, + &_executor_stmt_write_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "read", "()I", &_executor_read_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "close", JDBC_EXECUTOR_CLOSE_SIGNATURE, + &_executor_close_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "hasNext", JDBC_EXECUTOR_HAS_NEXT_SIGNATURE, + &_executor_has_next_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "getBlockAddress", "(ILjava/util/Map;)J", + &_executor_get_block_address_id)); RETURN_IF_ERROR( - register_id(_executor_clazz, "getCurBlockRows", "()I", _executor_block_rows_id)); - - RETURN_IF_ERROR(register_id(_executor_clazz, "openTrans", JDBC_EXECUTOR_TRANSACTION_SIGNATURE, - _executor_begin_trans_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "commitTrans", JDBC_EXECUTOR_TRANSACTION_SIGNATURE, - _executor_finish_trans_id)); - RETURN_IF_ERROR(register_id(_executor_clazz, "rollbackTrans", - JDBC_EXECUTOR_TRANSACTION_SIGNATURE, _executor_abort_trans_id)); - RETURN_IF_ERROR( - register_id(_executor_clazz, "testConnection", "()V", _executor_test_connection_id)); - RETURN_IF_ERROR( - register_id(_executor_clazz, "cleanDataSource", "()V", _executor_clean_datasource_id)); + _executor_clazz.get_method(env, "getCurBlockRows", "()I", &_executor_block_rows_id)); + + RETURN_IF_ERROR(_executor_clazz.get_method( + env, "openTrans", JDBC_EXECUTOR_TRANSACTION_SIGNATURE, &_executor_begin_trans_id)); + RETURN_IF_ERROR(_executor_clazz.get_method( + env, "commitTrans", JDBC_EXECUTOR_TRANSACTION_SIGNATURE, &_executor_finish_trans_id)); + RETURN_IF_ERROR(_executor_clazz.get_method( + env, "rollbackTrans", JDBC_EXECUTOR_TRANSACTION_SIGNATURE, &_executor_abort_trans_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "testConnection", "()V", + &_executor_test_connection_id)); + RETURN_IF_ERROR(_executor_clazz.get_method(env, "cleanDataSource", "()V", + &_executor_clean_datasource_id)); + return Status::OK(); } Status JdbcConnector::_get_reader_params(Block* block, JNIEnv* env, size_t column_size, - jobject* ans) { + Jni::LocalObject* ans) { std::ostringstream columns_nullable; std::ostringstream columns_replace_string; std::ostringstream required_fields; @@ -487,16 +456,17 @@ Status JdbcConnector::_get_reader_params(Block* block, JNIEnv* env, size_t colum {"replace_string", columns_replace_string.str()}, {"required_fields", required_fields.str()}, {"columns_types", columns_types.str()}}; - return JniUtil::convert_to_java_map(env, reader_params, ans); + return Jni::Util::convert_to_java_map(env, reader_params, ans); } Status JdbcConnector::_cast_string_to_special(Block* block, JNIEnv* env, size_t column_size) { for (size_t column_index = 0; column_index < column_size; ++column_index) { auto* slot_desc = _tuple_desc->slots()[column_index]; - jint num_rows = env->CallNonvirtualIntMethod(_executor_obj, _executor_clazz, - _executor_block_rows_id); - - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); + jint num_rows = 0; + RETURN_IF_ERROR( + _executor_obj + .call_nonvirtual_int_method(env, _executor_clazz, _executor_block_rows_id) + .call(&num_rows)); if (slot_desc->type()->get_primitive_type() == PrimitiveType::TYPE_HLL) { RETURN_IF_ERROR(_cast_string_to_hll(slot_desc, block, static_cast(column_index), @@ -649,16 +619,19 @@ Status JdbcConnector::_cast_string_to_json(const SlotDescriptor* slot_desc, Bloc } Status JdbcConnector::_get_java_table_type(JNIEnv* env, TOdbcTableType::type table_type, - jobject* java_enum_obj) { - jclass enum_class = env->FindClass("org/apache/doris/thrift/TOdbcTableType"); - jmethodID find_by_value_method = env->GetStaticMethodID( - enum_class, "findByValue", "(I)Lorg/apache/doris/thrift/TOdbcTableType;"); - jobject java_enum_local_obj = env->CallStaticObjectMethod(enum_class, find_by_value_method, - static_cast(table_type)); - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, java_enum_local_obj, java_enum_obj)); - env->DeleteLocalRef(java_enum_local_obj); - return Status::OK(); + Jni::LocalObject* java_enum_obj) { + Jni::LocalClass enum_class; + RETURN_IF_ERROR( + Jni::Util::find_class(env, "org/apache/doris/thrift/TOdbcTableType", &enum_class)); + + Jni::MethodId find_by_value_method; + RETURN_IF_ERROR(enum_class.get_static_method(env, "findByValue", + "(I)Lorg/apache/doris/thrift/TOdbcTableType;", + &find_by_value_method)); + + return enum_class.call_static_object_method(env, find_by_value_method) + .with_arg(static_cast(table_type)) + .call(java_enum_obj); } Status JdbcConnector::_get_real_url(const std::string& url, std::string* result_url) { diff --git a/be/src/vec/exec/vjdbc_connector.h b/be/src/vec/exec/vjdbc_connector.h index e210d3b95767ca..738cae8795c51c 100644 --- a/be/src/vec/exec/vjdbc_connector.h +++ b/be/src/vec/exec/vjdbc_connector.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -123,7 +124,7 @@ class JdbcConnector : public TableConnector { private: Status _register_func_id(JNIEnv* env); - Status _get_reader_params(Block* block, JNIEnv* env, size_t column_size, jobject* ans); + Status _get_reader_params(Block* block, JNIEnv* env, size_t column_size, Jni::LocalObject* ans); Status _cast_string_to_special(Block* block, JNIEnv* env, size_t column_size); Status _cast_string_to_hll(const SlotDescriptor* slot_desc, Block* block, int column_index, @@ -134,28 +135,29 @@ class JdbcConnector : public TableConnector { int rows); Status _get_java_table_type(JNIEnv* env, TOdbcTableType::type table_type, - jobject* java_enum_obj); + Jni::LocalObject* java_enum_obj); Status _get_real_url(const std::string& url, std::string* result_url); Status _check_and_return_default_driver_url(const std::string& url, std::string* result_url); bool _closed = false; - jclass _executor_factory_clazz; - jclass _executor_clazz; - jobject _executor_obj; - jmethodID _executor_factory_ctor_id; - jmethodID _executor_ctor_id; - jmethodID _executor_stmt_write_id; - jmethodID _executor_read_id; - jmethodID _executor_has_next_id; - jmethodID _executor_get_block_address_id; - jmethodID _executor_block_rows_id; - jmethodID _executor_close_id; - jmethodID _executor_begin_trans_id; - jmethodID _executor_finish_trans_id; - jmethodID _executor_abort_trans_id; - jmethodID _executor_test_connection_id; - jmethodID _executor_clean_datasource_id; + + Jni::GlobalClass _executor_factory_clazz; + Jni::GlobalClass _executor_clazz; + Jni::GlobalObject _executor_obj; + Jni::MethodId _executor_factory_ctor_id; + Jni::MethodId _executor_ctor_id; + Jni::MethodId _executor_stmt_write_id; + Jni::MethodId _executor_read_id; + Jni::MethodId _executor_has_next_id; + Jni::MethodId _executor_get_block_address_id; + Jni::MethodId _executor_block_rows_id; + Jni::MethodId _executor_close_id; + Jni::MethodId _executor_begin_trans_id; + Jni::MethodId _executor_finish_trans_id; + Jni::MethodId _executor_abort_trans_id; + Jni::MethodId _executor_test_connection_id; + Jni::MethodId _executor_clean_datasource_id; std::map _map_column_idx_to_cast_idx_hll; std::vector _input_hll_string_types; diff --git a/be/src/vec/exprs/table_function/udf_table_function.cpp b/be/src/vec/exprs/table_function/udf_table_function.cpp index 2cd212d3542a99..7bed98e12dfcb5 100644 --- a/be/src/vec/exprs/table_function/udf_table_function.cpp +++ b/be/src/vec/exprs/table_function/udf_table_function.cpp @@ -49,48 +49,36 @@ UDFTableFunction::UDFTableFunction(const TFunction& t_fn) : TableFunction(), _t_ Status UDFTableFunction::open() { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); _jni_ctx = std::make_shared(); - // Add a scoped cleanup jni reference object. This cleans up local refs made below. - JniLocalFrame jni_frame; - { - std::string local_location; - auto* function_cache = UserFunctionCache::instance(); - TJavaUdfExecutorCtorParams ctor_params; - ctor_params.__set_fn(_t_fn); - if (!_t_fn.hdfs_location.empty() && !_t_fn.checksum.empty()) { - // get jar path if both file path location and checksum are null - RETURN_IF_ERROR(function_cache->get_jarpath(_t_fn.id, _t_fn.hdfs_location, - _t_fn.checksum, &local_location)); - ctor_params.__set_location(local_location); - } - jbyteArray ctor_params_bytes; - // Pushed frame will be popped when jni_frame goes out-of-scope. - RETURN_IF_ERROR(jni_frame.push(env)); - RETURN_IF_ERROR(SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, EXECUTOR_CLASS, &_jni_ctx->executor_cl)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _jni_ctx->executor_ctor_id, env, - GetMethodID(_jni_ctx->executor_cl, "", EXECUTOR_CTOR_SIGNATURE)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _jni_ctx->executor_evaluate_id, env, - GetMethodID(_jni_ctx->executor_cl, "evaluate", EXECUTOR_EVALUATE_SIGNATURE)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _jni_ctx->executor_close_id, env, - GetMethodID(_jni_ctx->executor_cl, "close", EXECUTOR_CLOSE_SIGNATURE)); - - JNI_CALL_METHOD_CHECK_EXCEPTION( - , _jni_ctx->executor, env, - NewObject(_jni_ctx->executor_cl, _jni_ctx->executor_ctor_id, ctor_params_bytes)); - jbyte* pBytes = env->GetByteArrayElements(ctor_params_bytes, nullptr); - env->ReleaseByteArrayElements(ctor_params_bytes, pBytes, JNI_ABORT); - env->DeleteLocalRef(ctor_params_bytes); + std::string local_location; + auto* function_cache = UserFunctionCache::instance(); + TJavaUdfExecutorCtorParams ctor_params; + ctor_params.__set_fn(_t_fn); + if (!_t_fn.hdfs_location.empty() && !_t_fn.checksum.empty()) { + // get jar path if both file path location and checksum are null + RETURN_IF_ERROR(function_cache->get_jarpath(_t_fn.id, _t_fn.hdfs_location, _t_fn.checksum, + &local_location)); + ctor_params.__set_location(local_location); } - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, _jni_ctx->executor, &_jni_ctx->executor)); + RETURN_IF_ERROR(Jni::Util::find_class(env, EXECUTOR_CLASS, &_jni_ctx->executor_cl)); + + Jni::LocalArray ctor_params_bytes; + RETURN_IF_ERROR(Jni::Util::SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); + + RETURN_IF_ERROR(_jni_ctx->executor_cl.get_method(env, "", EXECUTOR_CTOR_SIGNATURE, + &_jni_ctx->executor_ctor_id)); + + RETURN_IF_ERROR(_jni_ctx->executor_cl.get_method(env, "evaluate", EXECUTOR_EVALUATE_SIGNATURE, + &_jni_ctx->executor_evaluate_id)); + + RETURN_IF_ERROR(_jni_ctx->executor_cl.get_method(env, "close", EXECUTOR_CLOSE_SIGNATURE, + &_jni_ctx->executor_close_id)); + + RETURN_IF_ERROR(_jni_ctx->executor_cl.new_object(env, _jni_ctx->executor_ctor_id) + .with_arg(ctor_params_bytes) + .call(&_jni_ctx->executor)); + _jni_ctx->open_successes = true; return Status::OK(); } @@ -107,7 +95,7 @@ Status UDFTableFunction::process_init(Block* block, RuntimeState* state) { child_column_idxs[i] = result_id; } JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); std::unique_ptr input_table; RETURN_IF_ERROR( JniConnector::to_java_table(block, block->rows(), child_column_idxs, input_table)); @@ -117,8 +105,8 @@ Status UDFTableFunction::process_init(Block* block, RuntimeState* state) { {"required_fields", input_table_schema.first}, {"columns_types", input_table_schema.second}}; - jobject input_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, input_params, &input_map)); + Jni::LocalObject input_map; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, input_params, &input_map)); _array_result_column = _return_type->create_column(); _result_column_idx = block->columns(); block->insert({_array_result_column, _return_type, "res"}); @@ -128,17 +116,13 @@ Status UDFTableFunction::process_init(Block* block, RuntimeState* state) { {"required_fields", output_table_schema.first}, {"columns_types", output_table_schema.second}}; - jobject output_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, output_params, &output_map)); - DCHECK(_jni_ctx != nullptr); - DCHECK(_jni_ctx->executor != nullptr); - long output_address = env->CallLongMethod(_jni_ctx->executor, _jni_ctx->executor_evaluate_id, - input_map, output_map); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(input_map); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(output_map); - RETURN_ERROR_IF_EXC(env); + Jni::LocalObject output_map; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, output_params, &output_map)); + long output_address; + RETURN_IF_ERROR(_jni_ctx->executor.call_long_method(env, _jni_ctx->executor_evaluate_id) + .with_arg(input_map) + .with_arg(output_map) + .call(&output_address)); RETURN_IF_ERROR(JniConnector::fill_block(block, {_result_column_idx}, output_address)); block->erase(_result_column_idx); if (!extract_column_array_info(*_array_result_column, _array_column_detail)) { diff --git a/be/src/vec/exprs/table_function/udf_table_function.h b/be/src/vec/exprs/table_function/udf_table_function.h index b25069dd1728c2..ae73d4d072ea3b 100644 --- a/be/src/vec/exprs/table_function/udf_table_function.h +++ b/be/src/vec/exprs/table_function/udf_table_function.h @@ -53,11 +53,12 @@ class UDFTableFunction final : public TableFunction { // Do not save parent directly, because parent is in VExpr, but jni context is in FunctionContext // The deconstruct sequence is not determined, it will core. // JniContext's lifecycle should same with function context, not related with expr - jclass executor_cl; - jmethodID executor_ctor_id; - jmethodID executor_evaluate_id; - jmethodID executor_close_id; - jobject executor = nullptr; + + Jni::GlobalClass executor_cl; + Jni::MethodId executor_ctor_id; + Jni::MethodId executor_evaluate_id; + Jni::MethodId executor_close_id; + Jni::GlobalObject executor; bool is_closed = false; bool open_successes = false; @@ -72,17 +73,15 @@ class UDFTableFunction final : public TableFunction { return Status::OK(); } JNIEnv* env = nullptr; - Status status = JniUtil::GetJNIEnv(&env); + Status status = Jni::Env::Get(&env); if (!status.ok() || env == nullptr) { LOG(WARNING) << "errors while get jni env " << status; return status; } - env->CallNonvirtualVoidMethodA(executor, executor_cl, executor_close_id, nullptr); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(executor); - RETURN_ERROR_IF_EXC(env); - env->DeleteGlobalRef(executor_cl); - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); + RETURN_IF_ERROR( + executor.call_nonvirtual_void_method(env, executor_cl, executor_close_id) + .call()); + is_closed = true; return Status::OK(); } diff --git a/be/src/vec/functions/function_java_udf.cpp b/be/src/vec/functions/function_java_udf.cpp index 7b4163e5c48ac5..c09fcda978eb73 100644 --- a/be/src/vec/functions/function_java_udf.cpp +++ b/be/src/vec/functions/function_java_udf.cpp @@ -43,15 +43,13 @@ JavaFunctionCall::JavaFunctionCall(const TFunction& fn, const DataTypes& argumen Status JavaFunctionCall::open(FunctionContext* context, FunctionContext::FunctionStateScope scope) { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); if (scope == FunctionContext::FunctionStateScope::THREAD_LOCAL) { SCOPED_TIMER(context->get_udf_execute_timer()); std::shared_ptr jni_ctx = std::make_shared(); context->set_function_state(FunctionContext::THREAD_LOCAL, jni_ctx); - // Add a scoped cleanup jni reference object. This cleans up local refs made below. - JniLocalFrame jni_frame; { std::string local_location; auto function_cache = UserFunctionCache::instance(); @@ -64,29 +62,20 @@ Status JavaFunctionCall::open(FunctionContext* context, FunctionContext::Functio ctor_params.__set_location(local_location); } - jbyteArray ctor_params_bytes; - - // Pushed frame will be popped when jni_frame goes out-of-scope. - RETURN_IF_ERROR(jni_frame.push(env)); - - RETURN_IF_ERROR(SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); - - RETURN_IF_ERROR(JniUtil::GetGlobalClassRef(env, EXECUTOR_CLASS, &jni_ctx->executor_cl)); - jni_ctx->executor_ctor_id = - env->GetMethodID(jni_ctx->executor_cl, "", EXECUTOR_CTOR_SIGNATURE); - jni_ctx->executor_evaluate_id = - env->GetMethodID(jni_ctx->executor_cl, "evaluate", EXECUTOR_EVALUATE_SIGNATURE); - jni_ctx->executor_close_id = - env->GetMethodID(jni_ctx->executor_cl, "close", EXECUTOR_CLOSE_SIGNATURE); - jni_ctx->executor = env->NewObject(jni_ctx->executor_cl, jni_ctx->executor_ctor_id, - ctor_params_bytes); - - jbyte* pBytes = env->GetByteArrayElements(ctor_params_bytes, nullptr); - env->ReleaseByteArrayElements(ctor_params_bytes, pBytes, JNI_ABORT); - env->DeleteLocalRef(ctor_params_bytes); + RETURN_IF_ERROR(Jni::Util::find_class(env, EXECUTOR_CLASS, &jni_ctx->executor_cl)); + + RETURN_IF_ERROR(jni_ctx->executor_cl.get_method(env, "", EXECUTOR_CTOR_SIGNATURE, + &jni_ctx->executor_ctor_id)); + RETURN_IF_ERROR(jni_ctx->executor_cl.get_method( + env, "evaluate", EXECUTOR_EVALUATE_SIGNATURE, &jni_ctx->executor_evaluate_id)); + RETURN_IF_ERROR(jni_ctx->executor_cl.get_method(env, "close", EXECUTOR_CLOSE_SIGNATURE, + &jni_ctx->executor_close_id)); + Jni::LocalArray ctor_params_bytes; + RETURN_IF_ERROR(Jni::Util::SerializeThriftMsg(env, &ctor_params, &ctor_params_bytes)); + RETURN_IF_ERROR(jni_ctx->executor_cl.new_object(env, jni_ctx->executor_ctor_id) + .with_arg(ctor_params_bytes) + .call(&jni_ctx->executor)); } - RETURN_ERROR_IF_EXC(env); - RETURN_IF_ERROR(JniUtil::LocalToGlobalRef(env, jni_ctx->executor, &jni_ctx->executor)); jni_ctx->open_successes = true; } return Status::OK(); @@ -96,7 +85,7 @@ Status JavaFunctionCall::execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t num_rows) const { JNIEnv* env = nullptr; - RETURN_IF_ERROR(JniUtil::GetJNIEnv(&env)); + RETURN_IF_ERROR(Jni::Env::Get(&env)); auto* jni_ctx = reinterpret_cast( context->get_function_state(FunctionContext::THREAD_LOCAL)); SCOPED_TIMER(context->get_udf_execute_timer()); @@ -107,21 +96,23 @@ Status JavaFunctionCall::execute_impl(FunctionContext* context, Block& block, {"meta_address", std::to_string((long)input_table.get())}, {"required_fields", input_table_schema.first}, {"columns_types", input_table_schema.second}}; - jobject input_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, input_params, &input_map)); + Jni::LocalObject input_map; + + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, input_params, &input_map)); auto output_table_schema = JniConnector::parse_table_schema(&block, {result}, true); std::string output_nullable = block.get_by_position(result).type->is_nullable() ? "true" : "false"; std::map output_params = {{"is_nullable", output_nullable}, {"required_fields", output_table_schema.first}, {"columns_types", output_table_schema.second}}; - jobject output_map = nullptr; - RETURN_IF_ERROR(JniUtil::convert_to_java_map(env, output_params, &output_map)); - long output_address = env->CallLongMethod(jni_ctx->executor, jni_ctx->executor_evaluate_id, - input_map, output_map); - env->DeleteGlobalRef(input_map); - env->DeleteGlobalRef(output_map); - RETURN_ERROR_IF_EXC(env); + Jni::LocalObject output_map; + RETURN_IF_ERROR(Jni::Util::convert_to_java_map(env, output_params, &output_map)); + long output_address = 0; + RETURN_IF_ERROR(jni_ctx->executor.call_long_method(env, jni_ctx->executor_evaluate_id) + .with_arg(input_map) + .with_arg(output_map) + .call(&output_address)); + return JniConnector::fill_block(&block, {result}, output_address); } diff --git a/be/src/vec/functions/function_java_udf.h b/be/src/vec/functions/function_java_udf.h index f0d41e7374018f..1e3fe4d9bb0c7d 100644 --- a/be/src/vec/functions/function_java_udf.h +++ b/be/src/vec/functions/function_java_udf.h @@ -114,11 +114,12 @@ class JavaFunctionCall : public IFunctionBase { // Do not save parent directly, because parent is in VExpr, but jni context is in FunctionContext // The deconstruct sequence is not determined, it will core. // JniContext's lifecycle should same with function context, not related with expr - jclass executor_cl; - jmethodID executor_ctor_id; - jmethodID executor_evaluate_id; - jmethodID executor_close_id; - jobject executor = nullptr; + + Jni::GlobalClass executor_cl; + Jni::MethodId executor_ctor_id; + Jni::MethodId executor_evaluate_id; + Jni::MethodId executor_close_id; + Jni::GlobalObject executor; bool is_closed = false; bool open_successes = false; @@ -134,17 +135,12 @@ class JavaFunctionCall : public IFunctionBase { } VLOG_DEBUG << "Free resources for JniContext"; JNIEnv* env = nullptr; - Status status = JniUtil::GetJNIEnv(&env); + Status status = Jni::Env::Get(&env); if (!status.ok() || env == nullptr) { LOG(WARNING) << "errors while get jni env " << status; return status; } - env->CallNonvirtualVoidMethodA(executor, executor_cl, executor_close_id, nullptr); - env->DeleteGlobalRef(executor); - env->DeleteGlobalRef(executor_cl); - RETURN_IF_ERROR(JniUtil::GetJniExceptionMsg(env)); - is_closed = true; - return Status::OK(); + return executor.call_nonvirtual_void_method(env, executor_cl, executor_close_id).call(); } }; }; diff --git a/be/test/io/fs/remote_file_system_test.cpp b/be/test/io/fs/remote_file_system_test.cpp index 309a31eb97f0f4..2a98b925a914f4 100644 --- a/be/test/io/fs/remote_file_system_test.cpp +++ b/be/test/io/fs/remote_file_system_test.cpp @@ -100,7 +100,6 @@ class RemoteFileSystemTest : public testing::Test { broker_addr.__set_hostname(broker_ip); broker_addr.__set_port(broker_port); - CHECK_STATUS_OK(doris::JniUtil::Init()); } virtual void TearDown() {} diff --git a/be/test/util/jni_util_test.cpp b/be/test/util/jni_util_test.cpp new file mode 100644 index 00000000000000..ddbed00c5d267e --- /dev/null +++ b/be/test/util/jni_util_test.cpp @@ -0,0 +1,682 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +#include + +#include +#include + +#include "util/jni-util.h" + +namespace doris { + +struct JavaMemInfo { + jlong used; + jlong free; + jlong total; + jlong max; +}; + +class JniUtilTest : public testing::Test { +public: + JniUtilTest() { + _test = false; + + // can not run as UT now, because ASAN will report memory leak. + // https://blog.gypsyengineer.com/en/security/running-java-with-addresssanitizer.html + // <> + // should export ASAN_OPTIONS=detect_leaks=0; + if (!_test) { + return; + } + + std::cout << "init = " << init(); + } + ~JniUtilTest() override = default; + + Status init() { + auto st = doris::Jni::Util::Init(); + + if (!st.ok()) { + exit(1); + } + JNIEnv* env; + + // jvm mem + RETURN_IF_ERROR(Jni::Env::Get(&env)); + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/Runtime", &runtime_cls)); + + RETURN_IF_ERROR(runtime_cls.get_static_method(env, "getRuntime", "()Ljava/lang/Runtime;", + &get_runtime)); + RETURN_IF_ERROR(runtime_cls.get_method(env, "totalMemory", "()J", &total_mem)); + RETURN_IF_ERROR(runtime_cls.get_method(env, "freeMemory", "()J", &free_mem)); + RETURN_IF_ERROR(runtime_cls.get_method(env, "maxMemory", "()J", &max_mem)); + + // gc + RETURN_IF_ERROR(Jni::Util::find_class(env, "java/lang/System", &system_cls)); + RETURN_IF_ERROR(system_cls.get_static_method(env, "gc", "()V", &gc_method)); + + RETURN_IF_ERROR(trigger_gc(env)); + + return Status::OK(); + } + + Status get_mem_info(JNIEnv* env, JavaMemInfo* info) { + Jni::LocalObject runtime_obj; + RETURN_IF_ERROR(runtime_cls.call_static_object_method(env, get_runtime).call(&runtime_obj)); + + jlong total, free, max, used; + RETURN_IF_ERROR(runtime_obj.call_long_method(env, total_mem).call(&total)); + RETURN_IF_ERROR(runtime_obj.call_long_method(env, free_mem).call(&free)); + RETURN_IF_ERROR(runtime_obj.call_long_method(env, max_mem).call(&max)); + used = total - free; + + info->used = used; + info->free = free; + info->total = total; + info->max = max; + + return Status::OK(); + } + + void print_mem_info(const std::string& title, const JavaMemInfo& info) { + std::cout << std::fixed << std::setprecision(2); + std::cout << "==== " << title << " ====\n"; + std::cout << "Used: " << (info.used / 1024.0 / 1024.0) << " MB\n"; + std::cout << "Free: " << (info.free / 1024.0 / 1024.0) << " MB\n"; + std::cout << "Total: " << (info.total / 1024.0 / 1024.0) << " MB\n"; + std::cout << "Max: " << (info.max / 1024.0 / 1024.0) << " MB\n"; + } + + Status trigger_gc(JNIEnv* env) { + RETURN_IF_ERROR(system_cls.call_static_void_method(env, gc_method).call()); + return Status::OK(); + } + + bool check_mem_diff(const JavaMemInfo& before, const JavaMemInfo& after, + double allowed_diff_mb) { + auto to_mb = [](jlong bytes) { return bytes / 1024.0 / 1024.0; }; + + double used_before = to_mb(before.used); + double used_after = to_mb(after.used); + + double diff = used_after - used_before; + + std::cout << std::fixed << std::setprecision(2); + std::cout << "==== Memory Diff Check ====\n"; + std::cout << "Used: " << used_before << " -> " << used_after << " (Δ=" << diff << " MB)\n"; + + if (diff > allowed_diff_mb) { + std::cout << "Memory change " << diff << " MB exceeds allowed " << allowed_diff_mb + << " MB\n"; + return false; + } + + std::cout << "Memory change within limit (" << diff << " MB ≤ " << allowed_diff_mb + << " MB)\n"; + return true; + } + +private: + Jni::GlobalClass runtime_cls; + Jni::GlobalClass system_cls; + + Jni::MethodId get_runtime; + Jni::MethodId total_mem; + Jni::MethodId free_mem; + Jni::MethodId max_mem; + + Jni::MethodId gc_method; + + bool _test; +}; + +TEST_F(JniUtilTest, MemoryStableAfterStringAllocations) { + if (!_test) { + std::cout << "Skip MemoryStableAfterStringAllocations test\n"; + return; + } + JNIEnv* env = nullptr; + auto st = Jni::Env::Get(&env); + EXPECT_TRUE(st.ok()) << st; + + JavaMemInfo info_before; + st = get_mem_info(env, &info_before); + ASSERT_TRUE(st.ok()) << st; + + print_mem_info("MemoryStableAfterStringAllocations_before", info_before); + for (int i = 0; i < 10000; i++) { + Jni::LocalClass string_cls; + st = Jni::Util::find_class(env, "java/lang/String", &string_cls); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId length_method; + st = string_cls.get_method(env, "length", "()I", &length_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId empty_method; + st = string_cls.get_method(env, "isEmpty", "()Z", &empty_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalString jstr_obj; + st = Jni::LocalString::new_string(env, "hello", &jstr_obj); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard str_buf; + st = jstr_obj.get_string_chars(env, &str_buf); + ASSERT_TRUE(st.ok()) << st; + + ASSERT_EQ(strlen(str_buf.get()), 5); + + ASSERT_EQ(strcmp(str_buf.get(), "hello"), 0); + + jint len; + st = jstr_obj.call_int_method(env, length_method).call(&len); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(len, 5); + + st = jstr_obj.call_int_method(env, length_method).call(); + ASSERT_TRUE(st.ok()) << st; + + jboolean empty; + st = jstr_obj.call_boolean_method(env, empty_method).call(&empty); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(empty, false); + } + + JavaMemInfo info_after; + st = get_mem_info(env, &info_after); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("MemoryStableAfterStringAllocations_after", info_after); + + st = trigger_gc(env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_after_gc; + st = get_mem_info(env, &info_after_gc); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("MemoryStableAfterStringAllocations_after_gc", info_after_gc); + + ASSERT_TRUE(check_mem_diff(info_before, info_after_gc, 5.0)); +} + +TEST_F(JniUtilTest, ByteBuffer) { + if (!_test) { + std::cout << "Skip ByteBuffer test\n"; + return; + } + JNIEnv* env = nullptr; + auto st = Jni::Env::Get(&env); + EXPECT_TRUE(st.ok()) << st; + + JavaMemInfo info_before; + st = get_mem_info(env, &info_before); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ByteBuffer_before", info_before); + + for (int i = 0; i < 300; i++) { // allocate 300M + Jni::LocalClass local_class; + st = Jni::Util::find_class(env, "java/nio/ByteBuffer", &local_class); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId allocate_method; + st = local_class.get_static_method(env, "allocate", "(I)Ljava/nio/ByteBuffer;", + &allocate_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalObject bytebuffer_obj; + st = local_class.call_static_object_method(env, allocate_method) + .with_arg(1024 * 1024) + .call(&bytebuffer_obj); // 1M + + ASSERT_TRUE(st.ok()) << st; + } + + JavaMemInfo info_after; + st = get_mem_info(env, &info_after); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ByteBuffer_after", info_after); + + st = trigger_gc(env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_after_gc; + st = get_mem_info(env, &info_after_gc); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ByteBuffer_after_gc", info_after_gc); + + ASSERT_TRUE(check_mem_diff(info_before, info_after_gc, 5.0)); +} + +TEST_F(JniUtilTest, StringMethodTest) { + if (!_test) { + std::cout << "Skip StringMethodTest test\n"; + return; + } + JNIEnv* env; + Status st = Jni::Env::Get(&env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_before; + st = get_mem_info(env, &info_before); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("String_before", info_before); + + Jni::LocalClass string_cls; + st = Jni::Util::find_class(env, "java/lang/String", &string_cls); + ASSERT_TRUE(st.ok()) << st; + + // get methodId + Jni::MethodId equals_method; + st = string_cls.get_method(env, "equals", "(Ljava/lang/Object;)Z", &equals_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId equals_ignore_case_method; + st = string_cls.get_method(env, "equalsIgnoreCase", "(Ljava/lang/String;)Z", + &equals_ignore_case_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId to_upper_method; + st = string_cls.get_method(env, "toUpperCase", "()Ljava/lang/String;", &to_upper_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId to_lower_method; + st = string_cls.get_method(env, "toLowerCase", "()Ljava/lang/String;", &to_lower_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId substring_method; + st = string_cls.get_method(env, "substring", "(II)Ljava/lang/String;", &substring_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId contains_method; + st = string_cls.get_method(env, "contains", "(Ljava/lang/CharSequence;)Z", &contains_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId starts_with_method; + st = string_cls.get_method(env, "startsWith", "(Ljava/lang/String;)Z", &starts_with_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId ends_with_method; + st = string_cls.get_method(env, "endsWith", "(Ljava/lang/String;)Z", &ends_with_method); + ASSERT_TRUE(st.ok()) << st; + + // 3. create some local string object. + Jni::LocalString str_hello, str_hello2, str_hello_upper, str_world; + st = Jni::LocalString::new_string(env, "hello", &str_hello); + ASSERT_TRUE(st.ok()) << st; + + st = Jni::LocalString::new_string(env, "HELLO", &str_hello_upper); + ASSERT_TRUE(st.ok()) << st; + + st = Jni::LocalString::new_string(env, "hello", &str_hello2); + ASSERT_TRUE(st.ok()) << st; + + st = Jni::LocalString::new_string(env, "world", &str_world); + ASSERT_TRUE(st.ok()) << st; + + for (int i = 0; i < 10000; i++) { + // 4. equals() and equalsIgnoreCase() + { + jboolean eq; + st = str_hello.call_boolean_method(env, equals_method).with_arg(str_hello2).call(&eq); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(eq, JNI_TRUE); // "hello".equals("hello") + + jboolean eq_ic; + st = str_hello.call_boolean_method(env, equals_ignore_case_method) + .with_arg(str_hello_upper) + .call(&eq_ic); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(eq_ic, JNI_TRUE); // "hello".equalsIgnoreCase("HELLO") + + jboolean neq; + st = str_hello.call_boolean_method(env, equals_method).with_arg(str_world).call(&neq); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(neq, JNI_FALSE); + } + + // 5. toUpperCase() / toLowerCase() + { + Jni::LocalString upper_obj; + st = str_hello.call_object_method(env, to_upper_method).call(&upper_obj); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard buf; + st = upper_obj.get_string_chars(env, &buf); + ASSERT_TRUE(st.ok()) << st; + ASSERT_STREQ(buf.get(), "HELLO"); + + Jni::LocalString lower_obj; + st = str_hello_upper.call_object_method(env, to_lower_method).call(&lower_obj); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard buf2; + st = lower_obj.get_string_chars(env, &buf2); + ASSERT_TRUE(st.ok()) << st; + ASSERT_STREQ(buf2.get(), "hello"); + } + + // 6. substring() test + { + Jni::LocalString sub_obj; + st = str_hello.call_object_method(env, substring_method) + .with_arg(1) + .with_arg(4) + .call(&sub_obj); // "ell" + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard buf; + st = sub_obj.get_string_chars(env, &buf); + ASSERT_TRUE(st.ok()) << st; + ASSERT_STREQ(buf.get(), "ell"); + } + + // 7. contains() / startsWith() / endsWith() hello + { + Jni::LocalString str_el; + st = Jni::LocalString::new_string(env, "el", &str_el); + ASSERT_TRUE(st.ok()) << st; + + jboolean contains; + st = str_hello.call_boolean_method(env, contains_method) + .with_arg(str_el) + .call(&contains); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(contains, true); + + Jni::LocalString str_he; + st = Jni::LocalString::new_string(env, "he", &str_he); + ASSERT_TRUE(st.ok()) << st; + + jboolean starts; + st = str_hello.call_boolean_method(env, starts_with_method) + .with_arg(str_he) + .call(&starts); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(starts, JNI_TRUE); + + Jni::LocalString str_ll; + st = Jni::LocalString::new_string(env, "ll", &str_ll); + ASSERT_TRUE(st.ok()) << st; + + jboolean ends; + st = str_hello.call_boolean_method(env, ends_with_method).with_arg(str_ll).call(&ends); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(ends, false); + } + } + + JavaMemInfo info_after; + st = get_mem_info(env, &info_after); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("String_after", info_after); + + st = trigger_gc(env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_after_gc; + st = get_mem_info(env, &info_after_gc); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("String_after_gc", info_after_gc); + + ASSERT_TRUE(check_mem_diff(info_before, info_after_gc, 5.0)); +} + +TEST_F(JniUtilTest, TestJavaDateArray) { + if (!_test) { + std::cout << "Skip TestJavaDateArray test\n"; + return; + } + JNIEnv* env; + Status st = Jni::Env::Get(&env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_before; + st = get_mem_info(env, &info_before); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("DateArray_before", info_before); + { + size_t test_size = 3; + + // Load java.util.Date class + Jni::LocalClass date_cls; + st = Jni::Util::find_class(env, "java/util/Date", &date_cls); + ASSERT_TRUE(st.ok()) << st; + + // Load java.lang.reflect.Array class + Jni::LocalClass reflect_array_cls; + st = Jni::Util::find_class(env, "java/lang/reflect/Array", &reflect_array_cls); + ASSERT_TRUE(st.ok()) << st; + + // Get static method Array.newInstance(Class, int) + Jni::MethodId new_instance_method; + st = reflect_array_cls.get_static_method( + env, "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", &new_instance_method); + ASSERT_TRUE(st.ok()) << st; + + Jni::MethodId set_method; + st = reflect_array_cls.get_static_method( + env, "set", "(Ljava/lang/Object;ILjava/lang/Object;)V", &set_method); + ASSERT_TRUE(st.ok()) << st; + + // Create a Date array: new Date[3] + Jni::LocalArray date_array; + st = reflect_array_cls.call_static_object_method(env, new_instance_method) + .with_arg(date_cls) + .with_arg((jint)test_size) + .call(&date_array); + ASSERT_TRUE(st.ok()) << st; + + // Get Date.() constructor + Jni::MethodId date_ctor; + st = date_cls.get_method(env, "", "()V", &date_ctor); + ASSERT_TRUE(st.ok()) << st; + + // Fill each element with new Date() + for (int i = 0; i < test_size; i++) { + Jni::LocalObject date_obj; + st = date_cls.new_object(env, date_ctor) + .with_arg((jlong)1700000000000LL) + .call(&date_obj); + ASSERT_TRUE(st.ok()) << st; + + st = reflect_array_cls.call_static_void_method(env, set_method) + .with_arg(date_array) + .with_arg(i) + .with_arg(date_obj) + .call(); + ASSERT_TRUE(st.ok()) << st; + } + + // Verify array length + jsize len; + st = date_array.get_length(env, &len); + ASSERT_TRUE(st.ok()) << st; + + ASSERT_EQ(len, test_size); + + // Get elements and print their toString() + Jni::MethodId toString_method; + st = date_cls.get_method(env, "toString", "()Ljava/lang/String;", &toString_method); + ASSERT_TRUE(st.ok()) << st; + + for (int i = 0; i < test_size; i++) { + Jni::LocalObject date_obj; + st = date_array.get_object_array_element(env, i, &date_obj); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalString date_str; + st = date_obj.call_object_method(env, toString_method).call(&date_str); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard str_buf; + st = date_str.get_string_chars(env, &str_buf); + ASSERT_TRUE(st.ok()) << st; + + std::cout << "Date[" << i << "] = " << str_buf.get() << std::endl; + } + } + + JavaMemInfo info_after; + st = get_mem_info(env, &info_after); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("DateArray_after", info_after); + + st = trigger_gc(env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_after_gc; + st = get_mem_info(env, &info_after_gc); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("DateArray_gc", info_after_gc); + + ASSERT_TRUE(check_mem_diff(info_before, info_after_gc, 5.0)); +} + +TEST_F(JniUtilTest, TestConvertMap) { + if (!_test) { + std::cout << "Skip TestConvertMap test\n"; + return; + } + JNIEnv* env; + Status st = Jni::Env::Get(&env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_before; + st = get_mem_info(env, &info_before); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ConvertMap_before", info_before); + + { + std::map cpp_map; + for (int i = 0; i < 10000; i++) { + cpp_map["key_" + std::to_string(i)] = "value_" + std::to_string(i); + } + + // Step 2: Convert to Java HashMap + Jni::LocalObject java_map; + st = Jni::Util::convert_to_java_map(env, cpp_map, &java_map); + ASSERT_TRUE(st.ok()) << st; + + // Step 3: Get java/util/HashMap class + Jni::LocalClass map_class; + st = Jni::Util::find_class(env, "java/util/HashMap", &map_class); + ASSERT_TRUE(st.ok()) << st; + + // Step 4: Prepare commonly used methods + Jni::MethodId size_method, get_method, contains_key_method, contains_value_method, + is_empty_method; + st = map_class.get_method(env, "size", "()I", &size_method); + ASSERT_TRUE(st.ok()) << st; + + st = map_class.get_method(env, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", + &get_method); + ASSERT_TRUE(st.ok()) << st; + + st = map_class.get_method(env, "containsKey", "(Ljava/lang/Object;)Z", + &contains_key_method); + ASSERT_TRUE(st.ok()) << st; + + st = map_class.get_method(env, "containsValue", "(Ljava/lang/Object;)Z", + &contains_value_method); + ASSERT_TRUE(st.ok()) << st; + + st = map_class.get_method(env, "isEmpty", "()Z", &is_empty_method); + ASSERT_TRUE(st.ok()) << st; + + jint size; + st = java_map.call_int_method(env, size_method).call(&size); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(size, (jint)cpp_map.size()); + + jboolean empty; + st = java_map.call_boolean_method(env, is_empty_method).call(&empty); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(empty, JNI_FALSE); + + // Step 6: Create test key and check containsKey / containsValue + Jni::LocalString test_key; + Jni::LocalString test_value; + st = Jni::LocalString::new_string(env, "key_1234", &test_key); + ASSERT_TRUE(st.ok()) << st; + + st = Jni::LocalString::new_string(env, "value_1234", &test_value); + ASSERT_TRUE(st.ok()) << st; + + jboolean has_key; + st = java_map.call_boolean_method(env, contains_key_method) + .with_arg(test_key) + .call(&has_key); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(has_key, JNI_TRUE); + + jboolean has_value; + st = java_map.call_boolean_method(env, contains_value_method) + .with_arg(test_value) + .call(&has_value); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(has_value, JNI_TRUE); + + Jni::LocalString result_obj; + st = java_map.call_object_method(env, get_method).with_arg(test_key).call(&result_obj); + ASSERT_TRUE(st.ok()) << st; + + Jni::LocalStringBufferGuard buf; + st = result_obj.get_string_chars(env, &buf); + ASSERT_TRUE(st.ok()) << st; + + ASSERT_STREQ(buf.get(), "value_1234"); + + Jni::LocalString fake_key; + st = Jni::String::new_string(env, "key_not_exist", &fake_key); + ASSERT_TRUE(st.ok()) << st; + + jboolean has_fake; + st = java_map.call_boolean_method(env, contains_key_method) + .with_arg(fake_key) + .call(&has_fake); + ASSERT_TRUE(st.ok()) << st; + ASSERT_EQ(has_fake, JNI_FALSE); + + std::map result_map; + st = Jni::Util::convert_to_cpp_map(env, java_map, &result_map); + + for (auto const& [a, b] : cpp_map) { + ASSERT_TRUE(result_map.contains(a)); + ASSERT_EQ(result_map[a], b); + } + } + + JavaMemInfo info_after; + st = get_mem_info(env, &info_after); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ConvertMap_after", info_after); + + st = trigger_gc(env); + ASSERT_TRUE(st.ok()) << st; + + JavaMemInfo info_after_gc; + st = get_mem_info(env, &info_after_gc); + ASSERT_TRUE(st.ok()) << st; + print_mem_info("ConvertMap_after_gc", info_after_gc); + + ASSERT_TRUE(check_mem_diff(info_before, info_after_gc, 5.0)); + + ASSERT_TRUE(Jni::Env::Get(&env).ok()); +} + +} // namespace doris