Coverage Report

Created: 2026-01-04 07:40

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