百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

JNI 跨线程调用时局部引用的陷阱_跨线程调用函数

myzbx 2025-09-01 09:52 5 浏览

一、背景

近期在项目中遇到一个困扰了2天的问题,虽然知道问题的原因,但始终不明所以,解决的方式也是囫囵吞枣。问题的起因是,项目中封装了一个统一的JNI 回调函数,将Native层C++ 调用Java 的函数统一接管,做过JNI 的都知道,Native调用Java 回调函数时需要考虑跨线程的情况,即在JNI 线程调用和不在JNI 线程调用的情况(有人喜欢把这类线程叫作子线程)。在JNI 线程时操作起来比较简单,直接调用即可。但不在JNI 线程时需要先对相关对象附在JNI 线程中,否则会闪退。

本文的问题在调用时已经考虑了线程的附加与分离,但依然报下述crash :

//(局部引用非法)
java_vm_ext.cc:598] JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid local reference: 0x7a38ba2035 (reference outside the table: 0x7a38ba2035) 
java_vm_ext.cc:598] JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid local reference: 0x7a38ba2035 (reference outside the table: 0x7a38ba2035)
                                                                                                    java_vm_ext.cc:598]     in call to GetObjectClass
2025-08-04 10:18:43.917 32203-7932  n.collaboration         com.tran.collaboration               A  runtime.cc:708] Runtime aborting...
                                                                                                    runtime.cc:708] Dumping all threads without mutator lock held
                                                                                                    runtime.cc:708] All threads:
                                                                                                    runtime.cc:708] DALVIK THREADS (26):
                                                                                                    runtime.cc:708] "main" prio=10 tid=1 Native
                                                                                                    runtime.cc:708]   | group="" sCount=1 ucsCount=0 flags=1 obj=0x72adc9e8 self=0xb40000784b84df50
                                                                                                    runtime.cc:708]   | sysTid=32203 nice=-10 cgrp=top-app sched=0/0 handle=0x7a3a0844f8
                                                                                                    runtime.cc:708]   | state=S schedstat=( 807140063 37525466 241 ) utm=68 stm=12 core=6 HZ=100

二、原因分析

分析这个问题之前,先将问题涉及的代码贴出来,这样好讲。

统一的回调函数:

JavaObjCtx.h

//
// Created by Rambo.liu on 2025/8/1.
// java object 管理器

#ifndef INPUTSHARE_JAVAOBJCTX_H
#define INPUTSHARE_JAVAOBJCTX_H


#include <jni.h>

class JavaObjCtx {
public:
    JNIEnv *env;
    //类对象
    jclass clazz;
    //构造器
    jmethodID constructor;
    //实例对象
    jobject instanceObj;
    //方法ID
    jmethodID methodId;
    JavaVM *jvm;

    /**
     * java 对象
     * @param _jvm
     * @param _env
     * @param clzz 类 需要包名 如:com/tran/share/input/Injection
     * @param methodName 方法名
     * @param methodSign 方法签名
     */
    JavaObjCtx(JavaVM *_jvm,JNIEnv *_env,const char* clzz,const char* methodName,const char* methodSign);

    ~JavaObjCtx();

    /**
     * 调用函数
     * @param obj 实例对象,需要注意跨线程调用,
     * 注意:obj 在跨线程调用时,当第一次在 JNI 线程调用时工作正常,但第二次在非 JNI 线程调用时,原始局部引用 jobject 已经失效。
     * 解决方案为:在首次获取 Java 对象时,直接转换为全局引用
     * 第二次调用时 obj 打印不为空但实际已失效,是 JNI 局部引用机制的一个典型陷阱
     * 当 JNI 方法返回时,整个局部引用表会被自动清空,但 C++ 变量 obj 仍保留原地址值
     * 类似「野指针」现象:指针地址非 NULL,但指向的内容已无效
     * @param methodID
     * @param ...
     */
    void callVoidMethod(jobject obj, jmethodID methodID, ...);


    bool isReferenceValid(JNIEnv* env, jobject obj);

};


#endif //INPUTSHARE_JAVAOBJCTX_H

JavaObjCtx.cpp

//
// Created by Rambo.liu on 2025/8/1.
//

#include "JavaObjCtx.h"
#include "Log.h"

JavaObjCtx::JavaObjCtx(JavaVM *_jvm,JNIEnv *_env, const char *clzz, const char *methodName,
                       const char *methodSign) {
    LOGI("class = %s  methodId = %s methodSign = %s", clzz, methodName, methodSign);
    env = _env;
    jvm = _jvm;
    //先找类对象
    jclass injectionCls = env->FindClass(clzz);
    if (injectionCls == nullptr) {
        LOGE("找不到 %s 类对象", clzz);
        return;
    }
    //找到类对象,先初始化,获取类对象实例,使用默认的构造方法
    jmethodID _constructor = env->GetMethodID(injectionCls, "<init>", "()V");
    if (nullptr == _constructor) {
        LOGW("can't constructor injectionCls");
        return;
    }
    //构造injectionCls 实例对象
    jobject injectionClsObject = env->NewObject(injectionCls, _constructor);
    if (nullptr == injectionClsObject) {
        LOGW("can't new injectionClsObject");
        env->DeleteLocalRef(injectionCls);
        return;
    }
    //查找要调用的方法
    jmethodID _methodId = env->GetMethodID(injectionCls, methodName,
                                           methodSign);
    if (_methodId == nullptr) {
        LOGV("onStatus 未找到,直接释放类对象与实例对象");
        env->DeleteLocalRef(injectionCls);
        env->DeleteLocalRef(injectionClsObject);
        return;
    }
    clazz = injectionCls;
    instanceObj = injectionClsObject;
    constructor = _constructor;
    methodId = _methodId;
}

JavaObjCtx::~JavaObjCtx() {
    LOGI("JavaObjCtx::~JavaObjCtx()析构");
    if (env) {
        if (clazz) {
            env->DeleteLocalRef(clazz);
        }
    }
}

void JavaObjCtx::callVoidMethod(jobject obj, jmethodID methodID, ...) {
    bool attached = false;
    if (!env) {
        LOGE("JNIEnv is null!");
        return;
    }

    if (!obj) {
        LOGE("Java object is null!");
        return;
    }

    // 检查线程是否已附加
    if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) {
        if (jvm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
            attached = true;
            LOGI("运行在非主线程中,附加成功");
        } else {
            LOGE("此函数运行在非创建JNI线程中,附加JVM 失败!!");
            return;
        }
    } else {
        LOGI("函数已运行在创建JNI 线程中,无需附加");
    }

    LOGD("跨线程地址: %p", obj); // 地址相同
    LOGD("是否有效: %d", isReferenceValid(env, obj)); // 必为false

    va_list args;
    va_start(args, methodID);
    env->CallVoidMethodV(obj, methodID, args);
    va_end(args);

    // 检查调用是否产生异常
    if (env->ExceptionCheck()) {
        LOGD("Exception occurred while calling method");
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    if (attached) {
        jvm->DetachCurrentThread();
    }
}

bool JavaObjCtx::isReferenceValid(JNIEnv *env, jobject obj) {
    if (!obj){
        LOGI("obj 已经为空");
        return false;
    }

    jclass objClass = env->GetObjectClass(obj); // 关键检查
    if (env->ExceptionCheck() || !objClass) {
        env->ExceptionClear();
        LOGI("obj 已经为空");
        return false;
    }
    return true;
}

出现问题的场景为:

  1. Socket 初始时针对Socket初始化各种失败的场景将结果回调给java 层做业务交互。这类场景都在JNI 线程中调用 callVoidMethod。调用的代码如下:

//回调函数
std::function<void(CONNECTED_STATUS status)> _connectCallBack;


//状态改变时  触发回调
void setStatus(CONNECTED_STATUS status) {
    _status = status;
    LOGI("80----------- status = %d",status);
    if(_connectCallBack) {
        _connectCallBack(status);
    }
}


void start(std::string &ip, const int port) {
    if (getStatus() == CONNECTED_STATUS::CONNECTING) {
        LOGI("正在连接中...");
        return;
    } else if (getStatus() == CONNECTED_STATUS::CONNECTED) {
        LOGI("已连接成功,无需要重复连接!!!");
        return;
    }
    setStatus(CONNECTED_STATUS::CONNECTING);

    _ip = ip;
    _port = port;
    // 1. 若已有线程在运行,先终止并清理
    if (_connect_thread && _connect_thread->joinable()) {
        _connect_thread->join(); // 等待旧线程结束
        _connect_thread.reset(); // 释放线程资源
    }
    _is_connected = false;
    _stop = true;

    //第二次创建线程调用socket connect 
    std::future<bool> connectFuture = commit(
            std::bind(&SocketClient::connectServer, this, ip, port));
    _is_connected = connectFuture.get();
    if (_is_connected) {
        _conn_cond.notify_one();
        std::packaged_task<bool()> task([this, ip, port]() {
            LOGI("连接成功,服务器地址:%s 端口:%d", ip.c_str(), port);
            _reconnect_count = 1;
            _stop = false;
            setStatus(CONNECTED_STATUS::CONNECTED);
            initEventLoop();
            read();
            return _is_connected;
        });
        _connect_thread = std::make_unique<std::thread>(std::move(task));
    }
}
  1. 第二次在Socket 连接服务器时调用,这次是开了一个线程来调用Socket 的connect,(非JNI 线程调用)callVoidMethod。(注 上述代码的红色的注释部分
  2. 外部调用时两个调用对象为同一个,调用部分代码为:
std::shared_ptr<SocketClient> _socketClient;
std::unique_ptr<JavaObjCtx> _javaObjCtx;

extern "C"
JNIEXPORT void JNICALL
testSocket(JNIEnv *env, jclass thiz,  jstring ip, jint port) {
    jboolean isCopy;
    const char * szIp = env->GetStringUTFChars(ip, &isCopy);
    if(!_socketClient) {
        _socketClient = std::make_shared<SocketClient>();
    }
    std::string s(szIp);
    if(!_javaObjCtx) {
        _javaObjCtx = std::make_unique<JavaObjCtx>(g_jvm,env,"com/tran/share/input/Injection","onStatus",
                                                   "(I)V");
       
    }
    
    //收到回调时由此统一向java 回吐
    _socketClient->setConnectStatusCallBack([&env](CONNECTED_STATUS status)->void {
        _javaObjCtx->callVoidMethod(_javaObjCtx->instanceObj, _javaObjCtx->methodId, status);
    });
    _socketClient->start(s , port);
    env->ReleaseStringUTFChars(ip, szIp);
}

Java 层的回调函数:

package com.tran.share.input;

import android.util.Log;

import java.io.DataOutputStream;

/**
 * author         rambo.liu
 * date           2025/7/15 11:04
 * Version:       1.0
 * Description:   事件注入器
 */
public class Injection {

    private final static String TAG = "Injection";


    static {
        System.loadLibrary("inputShare");
    }

    public void onStatus(int code) {
        Log.i(TAG,"58--------收到来自Native 的回调 code = "+code);
    }
}

业务需求很简单,但问题却不小。在JNI 对象中明明考虑了跨线程的场景,而且对相关的对象都做判空处理,

callVoidMethod 中判空代码

void JavaObjCtx::callVoidMethod(jobject obj, jmethodID methodID, ...) {
    bool attached = false;
    //判空
    if (!env) {
        LOGE("JNIEnv is null!");
        return;
    }
    //判空
    if (!obj) {
        LOGE("Java object is null!");
        return;
    }

    // 检查线程是否已附加
    if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) {
        if (jvm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
            attached = true;
            LOGI("运行在非主线程中,附加成功");
        } else {
            LOGE("此函数运行在非创建JNI线程中,附加JVM 失败!!");
            return;
        }
    } else {
        LOGI("函数已运行在创建JNI 线程中,无需附加");
    }
    va_list args;
    va_start(args, methodID);
    env->CallVoidMethodV(obj, methodID, args);
    va_end(args);

    // 检查调用是否产生异常
    if (env->ExceptionCheck()) {
        LOGD("Exception occurred while calling method");
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    if (attached) {
        jvm->DetachCurrentThread();
    }
}

为毛还报 "java_vm_ext.cc:598] JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid local reference: 0x7a38ba2035 (reference outside the table: 0x7a38ba2035) (局部引用非法)"

产生这个问题的根本原因:

当第一次在 JNI 线程调用时工作正常,但第二次在非 JNI 线程调用时,原始局部引用 jobject 已经失效。跨线程传递未保护的局部引用导致的。

  1. 第一次调用(JNI 线程)
  2. 局部引用 obj 有效,因为仍在原始 JNI 上下文生命周期内
  3. 第二次调用(非 JNI 线程)
  4. 局部引用 obj 已随第一次 JNI 调用结束被自动释放
  5. 直接使用会导致 invalid local reference 错误

这里还有一个问题需要搞清楚,不然没法理解:

既然已经失效了,按照常规理解,指针指向的内存应该也已经被释放了,callVoidMethod方法中判空应该会被阻断,但上述代码并非如此?何解?

第二次调用时callVoidMthod obj 打印不为空,其实是忽略了一个C/C++ 中的常见问题,即野指针问题。虽然指针指向的内存释放了,但指针并未指空,导致了该问题出现。这也是 JNI 局部引用机制的一个典型陷阱。以下是深度解析:

  1. 打印显示非空但实际无效
  2. C++ 层的 jobject 本质上是一个指针(类似 void*),打印时只是显示内存地址值
  3. 局部引用被释放后,指针地址不会自动置零,但 JVM 内部已将其标记为无效
  4. JNI 局部引用表机制

jobject obj = env->NewLocalRef(someObj); // 添加到当前线程的局部引用表

  • 当 JNI 方法返回时,整个局部引用表会被自动清空,但 C++ 变量 obj 仍保留原地址值
  • 类似「野指针」现象:指针地址非 NULL,但指向的内容已无效

为了验证上述分析,可以使用如下代码测试:

extern "C" JNIEXPORT void JNICALL 
Java_com_example_Test_printRef(JNIEnv* env, jobject thiz, jobject javaObj) {
    jobject localRef = env->NewLocalRef(javaObj);
    LOGD("第一次地址: %p", localRef); // 输出类似 0x7a38ba2039
    
    // 模拟跨线程传递(错误做法!)
    std::thread([=]{
        LOGD("第二次地址: %p", localRef); // 地址相同但已失效!
    }).join();
}

输出结果:

第一次地址: 0x7a38ba2039  
第二次地址: 0x7a38ba2039  // 相同地址但实际已失效

三、解决方案

既然局部引用已经 失效,但就转换为全局引用,将传递给callMethodId 方法的jobject 参数转换为实全局引用,调用的地方改为如下:

std::shared_ptr<SocketClient> _socketClient;
std::unique_ptr<JavaObjCtx> _javaObjCtx;
JavaVM *g_jvm = nullptr;
jobject g_globalObj = nullptr;
extern "C"
JNIEXPORT void JNICALL
testSocket(JNIEnv *env, jclass thiz,  jstring ip, jint port) {
    jboolean isCopy;
    const char * szIp = env->GetStringUTFChars(ip, &isCopy);
    if(!_socketClient) {
        _socketClient = std::make_shared<SocketClient>();
    }
    std::string s(szIp);
    if(!_javaObjCtx) {
        _javaObjCtx = std::make_unique<JavaObjCtx>(g_jvm,env,"com/tran/share/input/Injection","onStatus",
                                                   "(I)V");
        if (g_globalObj) env->DeleteGlobalRef(g_globalObj);
        //全局引用
        g_globalObj = env->NewGlobalRef(_javaObjCtx->instanceObj);
    }

    _socketClient->setConnectStatusCallBack([&env](CONNECTED_STATUS status)->void {
        //jobject 改为全局引用
        _javaObjCtx->callVoidMethod(g_globalObj, _javaObjCtx->methodId, status);
    });
    _socketClient->start(s , port);
    env->ReleaseStringUTFChars(ip, szIp);
}

//不用的时候记得释放全局指针 
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {
    LOGV("卸載---------如果有全局引用需要將其移除");
    JNIEnv* env;
    vm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (g_globalObj) {
        env->DeleteGlobalRef(g_globalObj);
        g_globalObj = nullptr;
    }
}

除了jobject 这类参数有此问题之外,还有一个对象在跨线程使用的过程中也容易忽视-- JNIEnv *env。在跨线程使用过程中需要注意以下几点:

  1. 线程安全

JNIEnv* 是线程私有对象,不能跨线程传递。如果在新线程中使用 env,必须先附加到 JVM 并获取当前线程的 env:按如下方式获取当前线程的JNIEnv 指针

// 在 C++ 新线程中获取 JNIEnv* 的正确方式
JNIEnv* env = nullptr;
// 假设 vm 是全局的 JavaVM* 指针(通过 JNI_OnLoad 获取)
int ret = vm->AttachCurrentThread(&env, nullptr);
if (ret != JNI_OK || env == nullptr) {
    LOGD("Failed to attach thread");
    return;
}

// 使用 env 前检查有效性
if (env->ExceptionCheck()) { ... }

// 线程结束时分离
vm->DetachCurrentThread();
  1. 生命周期:env 仅在当前 JNI 调用或附加的线程生命周期内有效,不要将其保存为全局变量长期使用。

在上述代码JavaObjCtx.h 中刚好把JNIEnv 当作成员变量了:

//
// Created by Rambo.liu on 2025/8/1.
// java object 管理器

#ifndef INPUTSHARE_JAVAOBJCTX_H
#define INPUTSHARE_JAVAOBJCTX_H


#include <jni.h>

class JavaObjCtx {
public:
    //成员变量
    JNIEnv *env;
    //类对象
    jclass clazz;
    //构造器
    jmethodID constructor;
    //实例对象
    jobject instanceObj;
    //方法ID
    jmethodID methodId;
    JavaVM *jvm;

};


#endif //INPUTSHARE_JAVAOBJCTX_H

源文件JavaObjCtx.cpp 中构造器中给JNIEnv* env赋值:

JavaObjCtx::JavaObjCtx(JavaVM *_jvm,JNIEnv *_env, const char *clzz, const char *methodName,
                       const char *methodSign) {
    LOGI("class = %s  methodId = %s methodSign = %s", clzz, methodName, methodSign);
    //给成员变量赋值
    env = _env; 
    jvm = _jvm;
    //先找类对象
   ......
}

这在跨线程调用函数:

void JavaObjCtx::callVoidMethod(jobject obj, jmethodID methodID, ...) {
    bool attached = false;
    if (!env) {
        return;
    }

    if (!obj) {
        return;
    }

    // 检查线程是否已附加
    if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) {
        if (jvm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
            attached = true;
            //LOGI("运行在非主线程中,附加成功");
        } else {
            //LOGE("此函数运行在非创建JNI线程中,附加JVM 失败!!");
            return;
        }
    } else {
        //LOGI("函数已运行在创建JNI 线程中,无需附加");
    }

    va_list args;
    va_start(args, methodID);
    env->CallVoidMethodV(obj, methodID, args);
    va_end(args);

    // 检查调用是否产生异常
    if (env->ExceptionCheck()) {
        LOGD("Exception occurred while calling method");
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    if (attached) {
        jvm->DetachCurrentThread();
    }
}

上述代码中使用的 env 由于是类的成员变量(而非局部变量),但 JNIEnv* 是线程私有的(每个线程有自己独立的 JNIEnv*),不能作为类成员跨线程共享。理由如下:

  • 当在新线程中调用此函数时,成员变量 env 可能仍是其他线程的无效值(甚至 nullptr)。
  • 即使通过 GetEnv 或 AttachCurrentThread 获取了当前线程的 env,若未正确更新成员变量,后续使用的仍是旧的无效 env。

如果在跨线程调用时,env 为成员变量同时又没有随线程更新,就会出现下面的crash 情况:

JNI 中  2025-08-04 19:15:16.227  8925-10816 libc                    com.tran.collaboration               A  Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 10816 (Thread-2), pid 8925 (n.collaboration)

Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 是 JNI 开发中常见的内存访问错误,本质是 C/C++ 代码尝试访问无效的内存地址(这里是 0x0,即空指针 NULL),导致操作系统触发段错误(Segmentation Fault)。

错误核心原因

SIGSEGV 是 Linux 系统的 “段错误” 信号,SEGV_MAPERR 表示程序尝试访问的内存地址未被映射到合法的内存空间,而 fault addr 0x0 明确指出:代码中存在对空指针(NULL)的解引用操作(比如访问 NULL->field 或 *(NULL))。

修复方案

核心原则:JNIEnv* 应作为局部变量使用,每次通过 JavaVM* 获取当前线程的有效实例,并严格检查所有指针的有效性。完整方案见文首的JavaObjCtx.h 代码


四、小结

场景

正确做法

需要跨线程使用

必须用 NewGlobalRef 转换

临时局部引用

确保不跨越 JNI 方法调用

调试检查

用 GetObjectClass 检测有效性

技术类比

JNI 概念

类似 C++ 概念

关键区别

局部引用

栈指针

自动管理生命周期

全局引用

new 分配的对象

需手动释放

弱全局引用

weak_ptr

可能被 GC 回收

绝对禁忌

// 错误!跨线程传递局部引用
void dangerousCall(jobject obj) {
    std::thread([=]{
        // 即使 obj 地址非空,实际已失效
        someJNICall(obj); 
    }).detach();
}

记住:在 JNI 中,指针非空 ≠ 引用有效!必须通过 JVM 机制验证

相关推荐

半导体行业术语缩写词典总结-JKL_半导体词汇缩写表

作为半导体行业新人来说,最痛苦的莫过于各种缩写词术语了,有的缩写词一样但是会有不同的解释。这里作者给大家整理了部分术语词典,后面会按照更新顺序一一分享出来。废话不多说,直接开始,如有遗漏,欢迎大家在评...

JD.com Deepens Push Into Embodied Intelligence With Investment in Sensor Maker PaXiniTech

ToraOne,thesecond-generationmultidimensionaltactilehumanoidrobotdevelopedbyPaXiniTechTMTPOS...

Hong Kong&#39;s Consumer Market Becomes New Battleground for Chinese Mainland Internet Giants

AI-generatedimageTMTPOST--StrollthroughthestreetsofHongKongtoday,anditmightfeellikey...

http2解决了哪些问题_简述http2的优点

HTTP/2(最初称为SPDY)是HTTP协议的第二个主要版本,它在HTTP/1.1的基础上进行了重大改进,旨在解决其在性能和效率方面的诸多瓶颈。以下是HTTP/2主要解决的问题:队头阻...

China&#39;s economy stays strong and vital amid pressure

Peoplevisitthe4thChina-CEECExpo&InternationalConsumerGoodsFairinNingbo,eastChina's...

JD.com Makes $2.4 Billion Bid for Ceconomy in Bold Push to Build a Global Retail Empire

TMTPOST--JD.comhasunveiledplanstoacquireGermany’sCeconomyAG—theparentofEurope’sleading...

深入剖析 Java 中的装饰器设计模式:原理、应用与实践

在Java软件开发的广阔天地里,设计模式犹如璀璨星辰,照亮我们构建高效、可维护系统的道路。今天,让我们聚焦于其中一颗闪耀的星——装饰器设计模式,深入探究它的奥秘,看看如何利用它为我们的代码赋予...

组合模式应用-适配器模式_适配器组件

写在前面Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!该部分为各模式组合使用,涉及代码较多,熟能生巧。内容回顾定义适配器模式是一种结构型设计模式,...

OOM (Out Of Memory) 故障排查指南

1.确认OOM类型首先需要确认是哪种类型的OOM:JavaHeapOOM:Java堆内存不足NativeMemoryOOM:本地内存不足MetaspaceOOM:元空间内存不足Contai...

刷完这49题,面试官当场给Offer!Java程序员必备指南

1.问题:如果main方法被声明为private会怎样?答案:能正常编译,但运行的时候会提示”main方法不是public的”。2.问题:Java里的传引用和传值的区别是什么?答案:传引用是指传递的是...

C#编程基础(看这一篇就够了)_c#编程入门与应用

C#及其开发环境简介C#概述C#是一个现代的、通用的、面向对象的编程语言,由微软(Microsoft)开发,经Ecma和ISO核准认可。它由AndersHejlsberg和他的团队在.NET框架开发...

说一下JDK的监控和 线上处理的一些case

一句话总结JDK监控常用工具包括JConsole、VisualVM、JMC等,用于实时查看内存、线程、GC状态。线上常见问题处理:内存泄漏通过heapdump分析对象引用链;频繁GC可调整-Xmx/...

JavaScript深拷贝极简指南:3种方法解决嵌套与循环引用难题

为什么需要深拷贝?首先我们看看浅拷贝,point指向的是同一个地址,这时我们修改obj2.point的属性时,obj1的point属性也会被修改再看看深拷贝,point指向的是不同地址,这时我们修改o...

Java 25 在 JEP 519 中集成了紧凑对象头

作者|ANMBazlurRahman译者|刘雅梦策划|丁晓昀Java25通过JEP519将紧凑对象头作为产品特性进行了集成,在不需要更改任何代码的情况下,为开发人员提供了...

每日一练 Python 面试题(1)_python每日一记

以下是5道Python基本语法相关的面试题,涵盖变量、运算符、数据结构、函数和异常处理等核心概念:1.变量与作用域题目:以下代码的输出是什么?解释原因。x=10deffunc():...