| // 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 "util/jni-util.h" |
| |
| #include <hdfs.h> |
| #include <sstream> |
| |
| #include "common/status.h" |
| #include "gutil/once.h" |
| #include "rpc/jni-thrift-util.h" |
| |
| #include "common/names.h" |
| |
| DEFINE_int64(jvm_deadlock_detector_interval_s, 60, |
| "(Advanced) Interval between JVM deadlock checks. If set to 0 or a negative value, " |
| "deadlock checks are disabled."); |
| |
| namespace impala { |
| |
| Status JniUtfCharGuard::create(JNIEnv* env, jstring jstr, JniUtfCharGuard* out) { |
| DCHECK(jstr != nullptr); |
| DCHECK(!env->ExceptionCheck()); |
| jboolean is_copy; |
| const char* utf_chars = env->GetStringUTFChars(jstr, &is_copy); |
| bool exception_check = static_cast<bool>(env->ExceptionCheck()); |
| if (utf_chars == nullptr || exception_check) { |
| if (exception_check) env->ExceptionClear(); |
| if (utf_chars != nullptr) env->ReleaseStringUTFChars(jstr, utf_chars); |
| auto fail_message = "GetStringUTFChars failed. Probable OOM on JVM side"; |
| LOG(ERROR) << fail_message; |
| return Status(fail_message); |
| } |
| out->env = env; |
| out->jstr = jstr; |
| out->utf_chars = utf_chars; |
| return Status::OK(); |
| } |
| |
| bool JniScopedArrayCritical::Create(JNIEnv* env, jbyteArray jarr, |
| JniScopedArrayCritical* out) { |
| DCHECK(env != nullptr); |
| DCHECK(out != nullptr); |
| DCHECK(!env->ExceptionCheck()); |
| int size = env->GetArrayLength(jarr); |
| void* pac = env->GetPrimitiveArrayCritical(jarr, nullptr); |
| if (pac == nullptr) { |
| LOG(ERROR) << "GetPrimitiveArrayCritical() failed. Probable OOM on JVM side"; |
| return false; |
| } |
| out->env_ = env; |
| out->jarr_ = jarr; |
| out->arr_ = static_cast<uint8_t*>(pac); |
| out->size_ = size; |
| return true; |
| } |
| |
| bool JniUtil::jvm_inited_ = false; |
| __thread JNIEnv* JniUtil::tls_env_ = nullptr; |
| jclass JniUtil::jni_util_cl_ = NULL; |
| jclass JniUtil::internal_exc_cl_ = NULL; |
| jmethodID JniUtil::get_jvm_metrics_id_ = NULL; |
| jmethodID JniUtil::get_jvm_threads_id_ = NULL; |
| jmethodID JniUtil::get_jmx_json_ = NULL; |
| jmethodID JniUtil::throwable_to_string_id_ = NULL; |
| jmethodID JniUtil::throwable_to_stack_trace_id_ = NULL; |
| |
| Status JniLocalFrame::push(JNIEnv* env, int max_local_ref) { |
| DCHECK(env_ == NULL); |
| DCHECK_GT(max_local_ref, 0); |
| if (env->PushLocalFrame(max_local_ref) < 0) { |
| env->ExceptionClear(); |
| return Status("failed to push frame"); |
| } |
| env_ = env; |
| return Status::OK(); |
| } |
| |
| bool JniUtil::ClassExists(JNIEnv* env, const char* class_str) { |
| jclass local_cl = env->FindClass(class_str); |
| jthrowable exc = env->ExceptionOccurred(); |
| if (exc != NULL) { |
| env->ExceptionClear(); |
| return false; |
| } |
| env->DeleteLocalRef(local_cl); |
| return true; |
| } |
| |
| bool JniUtil::MethodExists(JNIEnv* env, jclass class_ref, const char* method_str, |
| const char* method_signature) { |
| env->GetMethodID(class_ref, method_str, method_signature); |
| jthrowable exc = env->ExceptionOccurred(); |
| if (exc != nullptr) { |
| env->ExceptionClear(); |
| return false; |
| } |
| return true; |
| } |
| |
| Status JniUtil::GetGlobalClassRef(JNIEnv* env, const char* class_str, jclass* class_ref) { |
| *class_ref = NULL; |
| jclass local_cl = env->FindClass(class_str); |
| RETURN_ERROR_IF_EXC(env); |
| RETURN_IF_ERROR(LocalToGlobalRef(env, local_cl, class_ref)); |
| env->DeleteLocalRef(local_cl); |
| RETURN_ERROR_IF_EXC(env); |
| return Status::OK(); |
| } |
| |
| Status JniUtil::LocalToGlobalRef(JNIEnv* env, jobject local_ref, jobject* global_ref) { |
| *global_ref = env->NewGlobalRef(local_ref); |
| RETURN_ERROR_IF_EXC(env); |
| return Status::OK(); |
| } |
| |
| Status JniUtil::Init() { |
| InitLibhdfs(); |
| |
| // Get the JNIEnv* corresponding to current thread. |
| JNIEnv* env = JniUtil::GetJNIEnv(); |
| if (env == NULL) return Status("Failed to get/create JVM"); |
| // Find JniUtil class and create a global ref. |
| jclass local_jni_util_cl = env->FindClass("org/apache/impala/common/JniUtil"); |
| if (local_jni_util_cl == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil class."); |
| } |
| jni_util_cl_ = reinterpret_cast<jclass>(env->NewGlobalRef(local_jni_util_cl)); |
| if (jni_util_cl_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to create global reference to JniUtil class."); |
| } |
| env->DeleteLocalRef(local_jni_util_cl); |
| if (env->ExceptionOccurred()) { |
| return Status("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/impala/common/InternalException"); |
| if (local_internal_exc_cl == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil class."); |
| } |
| internal_exc_cl_ = reinterpret_cast<jclass>(env->NewGlobalRef(local_internal_exc_cl)); |
| if (internal_exc_cl_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to create global reference to JniUtil class."); |
| } |
| env->DeleteLocalRef(local_internal_exc_cl); |
| if (env->ExceptionOccurred()) { |
| return Status("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_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil.throwableToString method."); |
| } |
| |
| // throwableToStackTrace() |
| throwable_to_stack_trace_id_ = |
| env->GetStaticMethodID(jni_util_cl_, "throwableToStackTrace", |
| "(Ljava/lang/Throwable;)Ljava/lang/String;"); |
| if (throwable_to_stack_trace_id_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil.throwableToFullStackTrace method."); |
| } |
| |
| get_jvm_metrics_id_ = |
| env->GetStaticMethodID(jni_util_cl_, "getJvmMemoryMetrics", "()[B"); |
| if (get_jvm_metrics_id_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil.getJvmMemoryMetrics method."); |
| } |
| |
| get_jvm_threads_id_ = |
| env->GetStaticMethodID(jni_util_cl_, "getJvmThreadsInfo", "([B)[B"); |
| if (get_jvm_threads_id_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil.getJvmThreadsInfo method."); |
| } |
| |
| get_jmx_json_ = |
| env->GetStaticMethodID(jni_util_cl_, "getJMXJson", "()[B"); |
| if (get_jmx_json_ == NULL) { |
| if (env->ExceptionOccurred()) env->ExceptionDescribe(); |
| return Status("Failed to find JniUtil.getJMXJson method."); |
| } |
| jvm_inited_ = true; |
| return Status::OK(); |
| } |
| |
| void JniUtil::InitLibhdfs() { |
| // make random libhdfs calls to make sure that the context class loader isn't |
| // null; see xxx for an explanation |
| hdfsFS fs = hdfsConnect("default", 0); |
| hdfsDisconnect(fs); |
| } |
| |
| namespace { |
| JavaVM* g_vm; |
| GoogleOnceType g_vm_once = GOOGLE_ONCE_INIT; |
| |
| void FindJavaVMOrDie() { |
| int num_vms; |
| int rv = JNI_GetCreatedJavaVMs(&g_vm, 1, &num_vms); |
| CHECK_EQ(rv, 0) << "Could not find any created Java VM. Must init libhdfs first"; |
| CHECK_EQ(num_vms, 1) << "No VMs returned"; |
| } |
| |
| } // anonymous namespace |
| |
| JNIEnv* JniUtil::GetJNIEnvSlowPath() { |
| DCHECK(!tls_env_) << "Call GetJNIEnv() fast path"; |
| |
| GoogleOnceInit(&g_vm_once, &FindJavaVMOrDie); |
| int rc = g_vm->GetEnv(reinterpret_cast<void**>(&tls_env_), JNI_VERSION_1_6); |
| if (rc == JNI_EDETACHED) { |
| // Make a dummy call to attach libhdfs. |
| int junk; |
| hdfsConfGetInt("x", &junk); |
| rc = g_vm->GetEnv(reinterpret_cast<void**>(&tls_env_), JNI_VERSION_1_6); |
| } |
| CHECK_EQ(rc, 0) << "Unable to get JVM"; |
| return CHECK_NOTNULL(tls_env_); |
| } |
| |
| Status JniUtil::InitJvmPauseMonitor() { |
| JNIEnv* env = JniUtil::GetJNIEnv(); |
| if (!env) return Status("Failed to get/create JVM."); |
| if (!jni_util_cl_) return Status("JniUtil::Init() not called."); |
| jmethodID init_jvm_pm_method; |
| JniMethodDescriptor init_jvm_pm_desc = { |
| "initPauseMonitor", "(J)V", &init_jvm_pm_method}; |
| RETURN_IF_ERROR(JniUtil::LoadStaticJniMethod(env, jni_util_cl_, &init_jvm_pm_desc)); |
| return JniCall::static_method(jni_util_cl_, init_jvm_pm_method) |
| .with_primitive_arg(FLAGS_jvm_deadlock_detector_interval_s) |
| .Call(); |
| } |
| |
| 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<jstring>(env->CallStaticObjectMethod(jni_util_class(), |
| throwable_to_string_id(), exc)); |
| if (env->ExceptionOccurred()) { |
| env->ExceptionClear(); |
| string oom_msg = Substitute(oom_msg_template, "throwableToString"); |
| LOG(ERROR) << oom_msg; |
| return Status(oom_msg); |
| } |
| JniUtfCharGuard msg_str_guard; |
| RETURN_IF_ERROR(JniUtfCharGuard::create(env, msg, &msg_str_guard)); |
| if (log_stack) { |
| jstring stack = static_cast<jstring>(env->CallStaticObjectMethod(jni_util_class(), |
| throwable_to_stack_trace_id(), exc)); |
| if (env->ExceptionOccurred()) { |
| env->ExceptionClear(); |
| string oom_msg = Substitute(oom_msg_template, "throwableToStackTrace"); |
| LOG(ERROR) << oom_msg; |
| return Status(oom_msg); |
| } |
| JniUtfCharGuard c_stack_guard; |
| RETURN_IF_ERROR(JniUtfCharGuard::create(env, stack, &c_stack_guard)); |
| VLOG(1) << c_stack_guard.get(); |
| } |
| |
| env->DeleteLocalRef(exc); |
| return Status(Substitute("$0$1", prefix, msg_str_guard.get())); |
| } |
| |
| Status JniUtil::GetJvmMemoryMetrics(TGetJvmMemoryMetricsResponse* result) { |
| return JniCall::static_method(jni_util_class(), get_jvm_metrics_id_).Call(result); |
| } |
| |
| Status JniUtil::GetJvmThreadsInfo(const TGetJvmThreadsInfoRequest& request, |
| TGetJvmThreadsInfoResponse* result) { |
| return JniCall::static_method(jni_util_class(), get_jvm_threads_id_) |
| .with_thrift_arg(request).Call(result); |
| } |
| |
| Status JniUtil::GetJMXJson(TGetJMXJsonResponse* result) { |
| return JniCall::static_method(jni_util_class(), get_jmx_json_).Call(result); |
| } |
| |
| Status JniUtil::LoadJniMethod(JNIEnv* env, const jclass& jni_class, |
| JniMethodDescriptor* descriptor) { |
| (*descriptor->method_id) = env->GetMethodID(jni_class, |
| descriptor->name.c_str(), descriptor->signature.c_str()); |
| RETURN_ERROR_IF_EXC(env); |
| return Status::OK(); |
| } |
| |
| Status JniUtil::LoadStaticJniMethod(JNIEnv* env, const jclass& jni_class, |
| JniMethodDescriptor* descriptor) { |
| (*descriptor->method_id) = env->GetStaticMethodID(jni_class, |
| descriptor->name.c_str(), descriptor->signature.c_str()); |
| RETURN_ERROR_IF_EXC(env); |
| return Status::OK(); |
| } |
| } |