Coverage Report

Created: 2026-04-14 17:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/jni-util.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "util/jni-util.h"
19
20
#include <fmt/format.h>
21
#include <glog/logging.h>
22
#include <jni.h>
23
#include <jni_md.h>
24
25
#include <cstdlib>
26
#include <filesystem>
27
#include <iterator>
28
#include <memory>
29
#include <mutex>
30
#include <sstream>
31
#include <string>
32
#include <vector>
33
34
#include "absl/strings/substitute.h"
35
#include "common/cast_set.h"
36
#include "common/config.h"
37
#include "common/metrics/doris_metrics.h"
38
#include "util/jni_native_method.h"
39
// #include "util/libjvm_loader.h"
40
41
using std::string;
42
43
namespace doris {
44
namespace Jni {
45
JavaVM* g_vm;
46
[[maybe_unused]] std::once_flag g_vm_once;
47
[[maybe_unused]] std::once_flag g_jvm_conf_once;
48
__thread JNIEnv* Env::tls_env_ = nullptr;
49
jclass Env::jni_util_cl_ = nullptr;
50
jmethodID Env::throwable_to_string_id_ = nullptr;
51
jmethodID Env::throwable_to_stack_trace_id_ = nullptr;
52
53
0
const std::string GetDorisJNIDefaultClasspath() {
54
0
    const auto* doris_home = getenv("DORIS_HOME");
55
0
    DCHECK(doris_home) << "Environment variable DORIS_HOME is not set.";
56
57
0
    std::ostringstream out;
58
59
0
    auto add_jars_from_path = [&](const std::string& base_path) {
60
0
        if (!std::filesystem::exists(base_path)) {
61
0
            return;
62
0
        }
63
0
        for (const auto& entry : std::filesystem::recursive_directory_iterator(base_path)) {
64
0
            if (entry.path().extension() == ".jar") {
65
0
                if (!out.str().empty()) {
66
0
                    out << ":";
67
0
                }
68
0
                out << entry.path().string();
69
0
            }
70
0
        }
71
0
    };
72
73
0
    add_jars_from_path(std::string(doris_home) + "/lib");
74
0
    add_jars_from_path(std::string(doris_home) + "/custom_lib");
75
76
    // Check and add HADOOP_CONF_DIR if it's set
77
0
    const auto* hadoop_conf_dir = getenv("HADOOP_CONF_DIR");
78
0
    if (hadoop_conf_dir != nullptr && strlen(hadoop_conf_dir) > 0) {
79
0
        if (!out.str().empty()) {
80
0
            out << ":";
81
0
        }
82
0
        out << hadoop_conf_dir;
83
0
    }
84
85
0
    DCHECK(!out.str().empty()) << "Empty classpath is invalid.";
86
0
    return out.str();
87
0
}
88
89
0
const std::string GetDorisJNIClasspathOption() {
90
0
    const auto* classpath = getenv("DORIS_CLASSPATH");
91
0
    if (classpath) {
92
0
        return classpath;
93
0
    } else {
94
0
        return "-Djava.class.path=" + GetDorisJNIDefaultClasspath();
95
0
    }
96
0
}
97
98
0
const std::string GetKerb5ConfPath() {
99
0
    return "-Djava.security.krb5.conf=" + config::kerberos_krb5_conf_path;
100
0
}
101
102
0
[[maybe_unused]] void SetEnvIfNecessary() {
103
0
    std::string libhdfs_opts = getenv("LIBHDFS_OPTS") ? getenv("LIBHDFS_OPTS") : "";
104
0
    CHECK(libhdfs_opts != "") << "LIBHDFS_OPTS is not set";
105
0
    libhdfs_opts += fmt::format(" {} ", GetKerb5ConfPath());
106
0
    libhdfs_opts += fmt::format(" -Djdk.lang.processReaperUseDefaultStackSize={}",
107
0
                                config::jdk_process_reaper_use_default_stack_size);
108
0
    setenv("LIBHDFS_OPTS", libhdfs_opts.c_str(), 1);
109
0
    LOG(INFO) << "set final LIBHDFS_OPTS: " << libhdfs_opts;
110
0
}
111
112
// Only used on non-x86 platform
113
0
[[maybe_unused]] void FindOrCreateJavaVM() {
114
0
    int num_vms;
115
0
    int rv = JNI_GetCreatedJavaVMs(&g_vm, 1, &num_vms);
116
0
    if (rv == 0) {
117
0
        std::vector<std::string> options;
118
119
0
        char* java_opts = getenv("JAVA_OPTS");
120
0
        if (java_opts == nullptr) {
121
0
            options = {
122
0
                    GetDorisJNIClasspathOption(), fmt::format("-Xmx{}", "1g"),
123
0
                    fmt::format("-DlogPath={}/log/jni.log", getenv("DORIS_HOME")),
124
0
                    fmt::format("-Dsun.java.command={}", "DorisBE"), "-XX:-CriticalJNINatives",
125
0
                    fmt::format("-Djdk.lang.processReaperUseDefaultStackSize={}",
126
0
                                config::jdk_process_reaper_use_default_stack_size),
127
#ifdef __APPLE__
128
                    // On macOS, we should disable MaxFDLimit, otherwise the RLIMIT_NOFILE
129
                    // will be assigned the minimum of OPEN_MAX (10240) and rlim_cur (See src/hotspot/os/bsd/os_bsd.cpp)
130
                    // and it can not pass the check performed by storage engine.
131
                    // The newer JDK has fixed this issue.
132
                    "-XX:-MaxFDLimit"
133
#endif
134
0
            };
135
0
        } else {
136
0
            std::istringstream stream(java_opts);
137
0
            options = std::vector<std::string>(std::istream_iterator<std::string> {stream},
138
0
                                               std::istream_iterator<std::string>());
139
0
            options.push_back(GetDorisJNIClasspathOption());
140
0
        }
141
0
        options.push_back(GetKerb5ConfPath());
142
0
        std::unique_ptr<JavaVMOption[]> jvm_options(new JavaVMOption[options.size()]);
143
0
        for (int i = 0; i < options.size(); ++i) {
144
            // To convert a string to a char*, const_cast is used.
145
0
            jvm_options[i] = {const_cast<char*>(options[i].c_str()), nullptr};
146
0
        }
147
148
0
        JNIEnv* env = nullptr;
149
0
        JavaVMInitArgs vm_args;
150
0
        vm_args.version = JNI_VERSION_1_8;
151
0
        vm_args.options = jvm_options.get();
152
0
        vm_args.nOptions = cast_set<int>(options.size());
153
        // Set it to JNI_FALSE because JNI_TRUE will let JVM ignore the max size config.
154
0
        vm_args.ignoreUnrecognized = JNI_FALSE;
155
156
0
        jint res = JNI_CreateJavaVM(&g_vm, (void**)&env, &vm_args);
157
0
        if (JNI_OK != res) {
158
0
            DCHECK(false) << "Failed to create JVM, code= " << res;
159
0
        }
160
161
0
    } else {
162
0
        CHECK_EQ(rv, 0) << "Could not find any created Java VM";
163
0
        CHECK_EQ(num_vms, 1) << "No VMs returned";
164
0
    }
165
0
}
166
167
0
Status Env::GetJNIEnvSlowPath(JNIEnv** env) {
168
0
    DCHECK(!tls_env_) << "Call GetJNIEnv() fast path";
169
170
#ifdef USE_LIBHDFS3
171
    std::call_once(g_vm_once, FindOrCreateJavaVM);
172
    int rc = g_vm->GetEnv(reinterpret_cast<void**>(&tls_env_), JNI_VERSION_1_8);
173
    if (rc == JNI_EDETACHED) {
174
        rc = g_vm->AttachCurrentThread((void**)&tls_env_, nullptr);
175
    }
176
    if (rc != 0 || tls_env_ == nullptr) {
177
        return Status::JniError("Unable to get JVM: {}", rc);
178
    }
179
#else
180
    // the hadoop libhdfs will do all the stuff
181
0
    std::call_once(g_jvm_conf_once, SetEnvIfNecessary);
182
0
    tls_env_ = getJNIEnv();
183
0
#endif
184
0
    *env = tls_env_;
185
0
    return Status::OK();
186
0
}
187
188
0
Status Env::GetJniExceptionMsg(JNIEnv* env, bool log_stack, const string& prefix) {
189
0
    jthrowable exc = env->ExceptionOccurred();
190
0
    Defer def {[&]() { env->DeleteLocalRef(exc); }};
191
0
    if (exc == nullptr) {
192
0
        return Status::OK();
193
0
    }
194
0
    env->ExceptionClear();
195
0
    DCHECK(throwable_to_string_id_ != nullptr);
196
0
    const char* oom_msg_template =
197
0
            "$0 threw an unchecked exception. The JVM is likely out "
198
0
            "of memory (OOM).";
199
0
    jstring msg = static_cast<jstring>(
200
0
            env->CallStaticObjectMethod(jni_util_cl_, throwable_to_string_id_, exc));
201
0
    if (env->ExceptionOccurred()) {
202
0
        env->ExceptionClear();
203
0
        string oom_msg = absl::Substitute(oom_msg_template, "throwableToString");
204
0
        LOG(WARNING) << oom_msg;
205
0
        return Status::JniError(oom_msg);
206
0
    }
207
208
0
    std::string return_msg;
209
0
    auto* msg_str = env->GetStringUTFChars(msg, nullptr);
210
0
    return_msg += msg_str;
211
0
    env->ReleaseStringUTFChars((jstring)msg, msg_str);
212
213
0
    if (log_stack) {
214
0
        jstring stack = static_cast<jstring>(
215
0
                env->CallStaticObjectMethod(jni_util_cl_, throwable_to_stack_trace_id_, exc));
216
0
        if (env->ExceptionOccurred()) {
217
0
            env->ExceptionClear();
218
0
            string oom_msg = absl::Substitute(oom_msg_template, "throwableToStackTrace");
219
0
            LOG(WARNING) << oom_msg;
220
0
            return Status::JniError(oom_msg);
221
0
        }
222
223
0
        auto* stask_str = env->GetStringUTFChars(stack, nullptr);
224
0
        LOG(WARNING) << stask_str;
225
0
        env->ReleaseStringUTFChars(stack, stask_str);
226
0
    }
227
228
0
    return Status::JniError("{}{}", prefix, return_msg);
229
0
}
230
231
bool Util::jvm_inited_ = false;
232
233
jlong Util::max_jvm_heap_memory_size_ = 0;
234
GlobalObject Util::jni_scanner_loader_obj_;
235
MethodId Util::jni_scanner_loader_method_;
236
MethodId Util::_clean_udf_cache_method_id;
237
GlobalClass Util::hashmap_class;
238
MethodId Util::hashmap_constructor;
239
MethodId Util::hashmap_put;
240
GlobalClass Util::mapClass;
241
MethodId Util::mapEntrySetMethod;
242
GlobalClass Util::mapEntryClass;
243
MethodId Util::getEntryKeyMethod;
244
MethodId Util::getEntryValueMethod;
245
GlobalClass Util::setClass;
246
MethodId Util::iteratorSetMethod;
247
GlobalClass Util::iteratorClass;
248
MethodId Util::iteratorHasNextMethod;
249
MethodId Util::iteratorNextMethod;
250
251
0
void Util::_parse_max_heap_memory_size_from_jvm() {
252
    // The start_be.sh would set JAVA_OPTS inside LIBHDFS_OPTS
253
0
    std::string java_opts = getenv("LIBHDFS_OPTS") ? getenv("LIBHDFS_OPTS") : "";
254
0
    std::istringstream iss(java_opts);
255
0
    std::string opt;
256
0
    while (iss >> opt) {
257
0
        if (opt.find("-Xmx") == 0) {
258
0
            std::string xmxValue = opt.substr(4);
259
0
            LOG(INFO) << "The max heap vaule is " << xmxValue;
260
0
            char unit = xmxValue.back();
261
0
            xmxValue.pop_back();
262
0
            long long value = std::stoll(xmxValue);
263
0
            switch (unit) {
264
0
            case 'g':
265
0
            case 'G':
266
0
                max_jvm_heap_memory_size_ = value * 1024 * 1024 * 1024;
267
0
                break;
268
0
            case 'm':
269
0
            case 'M':
270
0
                max_jvm_heap_memory_size_ = value * 1024 * 1024;
271
0
                break;
272
0
            case 'k':
273
0
            case 'K':
274
0
                max_jvm_heap_memory_size_ = value * 1024;
275
0
                break;
276
0
            default:
277
0
                max_jvm_heap_memory_size_ = value;
278
0
                break;
279
0
            }
280
0
        }
281
0
    }
282
0
    if (0 == max_jvm_heap_memory_size_) {
283
0
        LOG(FATAL) << "the max_jvm_heap_memory_size_ is " << max_jvm_heap_memory_size_;
284
0
    }
285
0
    LOG(INFO) << "the max_jvm_heap_memory_size_ is " << max_jvm_heap_memory_size_;
286
0
}
287
288
0
size_t Util::get_max_jni_heap_memory_size() {
289
0
#if defined(USE_LIBHDFS3) || defined(BE_TEST)
290
0
    return std::numeric_limits<size_t>::max();
291
#else
292
    static std::once_flag _parse_max_heap_memory_size_from_jvm_flag;
293
    std::call_once(_parse_max_heap_memory_size_from_jvm_flag, _parse_max_heap_memory_size_from_jvm);
294
    return max_jvm_heap_memory_size_;
295
#endif
296
0
}
297
298
0
Status Util::_init_jni_scanner_loader() {
299
0
    JNIEnv* env = nullptr;
300
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
301
0
    LocalClass jni_scanner_loader_cls;
302
0
    std::string jni_scanner_loader_str = "org/apache/doris/common/classloader/ScannerLoader";
303
0
    RETURN_IF_ERROR(find_class(env, jni_scanner_loader_str.c_str(), &jni_scanner_loader_cls));
304
305
0
    MethodId jni_scanner_loader_constructor;
306
0
    RETURN_IF_ERROR(jni_scanner_loader_cls.get_method(env, "<init>", "()V",
307
0
                                                      &jni_scanner_loader_constructor));
308
309
0
    RETURN_IF_ERROR(jni_scanner_loader_cls.get_method(env, "getLoadedClass",
310
0
                                                      "(Ljava/lang/String;)Ljava/lang/Class;",
311
0
                                                      &jni_scanner_loader_method_));
312
313
0
    MethodId load_jni_scanner;
314
0
    RETURN_IF_ERROR(
315
0
            jni_scanner_loader_cls.get_method(env, "loadAllScannerJars", "()V", &load_jni_scanner));
316
317
0
    RETURN_IF_ERROR(jni_scanner_loader_cls.get_method(
318
0
            env, "cleanUdfClassLoader", "(Ljava/lang/String;)V", &_clean_udf_cache_method_id));
319
320
0
    RETURN_IF_ERROR(jni_scanner_loader_cls.new_object(env, jni_scanner_loader_constructor)
321
0
                            .call(&jni_scanner_loader_obj_));
322
323
0
    RETURN_IF_ERROR(jni_scanner_loader_obj_.call_void_method(env, load_jni_scanner).call());
324
0
    return Status::OK();
325
0
}
326
327
0
Status Util::clean_udf_class_load_cache(const std::string& function_signature) {
328
0
    JNIEnv* env = nullptr;
329
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
330
331
0
    LocalString function_signature_jstr;
332
0
    RETURN_IF_ERROR(
333
0
            LocalString::new_string(env, function_signature.c_str(), &function_signature_jstr));
334
335
0
    RETURN_IF_ERROR(jni_scanner_loader_obj_.call_void_method(env, _clean_udf_cache_method_id)
336
0
                            .with_arg(function_signature_jstr)
337
0
                            .call());
338
339
0
    return Status::OK();
340
0
}
341
342
0
Status Util::_init_collect_class() {
343
0
    JNIEnv* env = nullptr;
344
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
345
    // for hashmap
346
0
    RETURN_IF_ERROR(find_class(env, "java/util/HashMap", &hashmap_class));
347
0
    RETURN_IF_ERROR(hashmap_class.get_method(env, "<init>", "(I)V", &hashmap_constructor));
348
0
    RETURN_IF_ERROR(hashmap_class.get_method(
349
0
            env, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", &hashmap_put));
350
351
    //for map
352
0
    RETURN_IF_ERROR(find_class(env, "java/util/Map", &mapClass));
353
0
    RETURN_IF_ERROR(mapClass.get_method(env, "entrySet", "()Ljava/util/Set;", &mapEntrySetMethod));
354
355
    //for set
356
0
    RETURN_IF_ERROR(find_class(env, "java/util/Set", &setClass));
357
0
    RETURN_IF_ERROR(
358
0
            setClass.get_method(env, "iterator", "()Ljava/util/Iterator;", &iteratorSetMethod));
359
360
    // for iterator
361
0
    RETURN_IF_ERROR(find_class(env, "java/util/Iterator", &iteratorClass));
362
0
    RETURN_IF_ERROR(iteratorClass.get_method(env, "hasNext", "()Z", &iteratorHasNextMethod));
363
0
    RETURN_IF_ERROR(
364
0
            iteratorClass.get_method(env, "next", "()Ljava/lang/Object;", &iteratorNextMethod));
365
366
    //for map entry
367
0
    RETURN_IF_ERROR(find_class(env, "java/util/Map$Entry", &mapEntryClass));
368
0
    RETURN_IF_ERROR(
369
0
            mapEntryClass.get_method(env, "getKey", "()Ljava/lang/Object;", &getEntryKeyMethod));
370
371
0
    RETURN_IF_ERROR(mapEntryClass.get_method(env, "getValue", "()Ljava/lang/Object;",
372
0
                                             &getEntryValueMethod));
373
374
0
    return Status::OK();
375
0
}
376
377
0
Status Util::Init() {
378
0
    RETURN_IF_ERROR(Env::Init());
379
0
    RETURN_IF_ERROR(_init_register_natives());
380
0
    RETURN_IF_ERROR(_init_collect_class());
381
0
    RETURN_IF_ERROR(_init_jni_scanner_loader());
382
0
    jvm_inited_ = true;
383
0
    DorisMetrics::instance()->init_jvm_metrics();
384
0
    return Status::OK();
385
0
}
386
387
0
Status Util::_init_register_natives() {
388
0
    JNIEnv* env = nullptr;
389
0
    RETURN_IF_ERROR(Jni::Env::Get(&env));
390
    // Find JNINativeMethod class and create a global ref.
391
0
    jclass local_jni_native_exc_cl =
392
0
            env->FindClass("org/apache/doris/common/jni/utils/JNINativeMethod");
393
0
    if (local_jni_native_exc_cl == nullptr) {
394
0
        if (env->ExceptionOccurred()) {
395
0
            env->ExceptionDescribe();
396
0
        }
397
0
        return Status::JniError("Failed to find JNINativeMethod class.");
398
0
    }
399
400
0
    static char memory_alloc_name[] = "memoryTrackerMalloc";
401
0
    static char memory_alloc_sign[] = "(J)J";
402
0
    static char memory_free_name[] = "memoryTrackerFree";
403
0
    static char memory_free_sign[] = "(J)V";
404
0
    static char memory_alloc_batch_name[] = "memoryTrackerMallocBatch";
405
0
    static char memory_alloc_batch_sign[] = "([I)[J";
406
0
    static char memory_free_batch_name[] = "memoryTrackerFreeBatch";
407
0
    static char memory_free_batch_sign[] = "([J)V";
408
0
    static JNINativeMethod java_native_methods[] = {
409
0
            {memory_alloc_name, memory_alloc_sign, (void*)&JavaNativeMethods::memoryMalloc},
410
0
            {memory_free_name, memory_free_sign, (void*)&JavaNativeMethods::memoryFree},
411
0
            {memory_alloc_batch_name, memory_alloc_batch_sign,
412
0
             (void*)&JavaNativeMethods::memoryMallocBatch},
413
0
            {memory_free_batch_name, memory_free_batch_sign,
414
0
             (void*)&JavaNativeMethods::memoryFreeBatch},
415
0
    };
416
417
0
    int res = env->RegisterNatives(local_jni_native_exc_cl, java_native_methods,
418
0
                                   sizeof(java_native_methods) / sizeof(java_native_methods[0]));
419
0
    DCHECK_EQ(res, 0);
420
0
    if (res) [[unlikely]] {
421
0
        return Status::JniError("Failed to RegisterNatives.");
422
0
    }
423
0
    return Status::OK();
424
0
}
425
426
} // namespace Jni
427
} // namespace doris