0

0

如何使用c++和JNI (Java Native Interface) 进行Android NDK开发? (性能优化)

尼克

尼克

发布时间:2026-01-17 16:19:45

|

506人浏览过

|

来源于php中文网

原创

JNIEnv 必须每次调用时重新获取,因其是线程局部的;JavaVM 可全局缓存,但 JNIEnv* 跨线程复用会导致崩溃;正确做法是通过 GetEnv 或 AttachCurrentThread 获取,并在 native 线程结束时 Detach。

如何使用c++和jni (java native interface) 进行android ndk开发? (性能优化)

为什么 JNIEnv* 必须在每个 JNI 函数调用中重新获取,不能跨线程缓存?

Android 的 JNI 环境(JNIEnv*)是线程局部的,不是全局可复用的指针。很多开发者误以为在 Java_com_example_NativeLib_init 中保存一次 JNIEnv*,后续就能直接用——这会导致崩溃或随机内存错误,尤其在回调、异步线程或 onDraw 频繁调用场景下。

正确做法是:每次进入 JNI 函数时,通过 JavaVM->GetEnv()AttachCurrentThread() 获取当前线程有效的 JNIEnv*。如果线程未附加(如 native 线程刚创建),必须先 AttachCurrentThread;用完后,若该线程由 native 创建,建议显式 DetachCurrentThread(避免线程资源泄漏)。

  • JavaVM* 可安全全局缓存(在 JNI_OnLoad 中获取并存为静态变量)
  • 不要在 C++ 成员变量或 static 指针中长期持有 JNIEnv*
  • Android 12+ 对未 detach 的线程更敏感,可能触发 ANR 或 java.lang.OutOfMemoryError: pthread_create failed

如何避免频繁 FindClassGetMethodID 导致的性能瓶颈

FindClass 是 JNI 中开销最大的操作之一,它要遍历类路径、解析字节码、触发类加载——在循环或高频回调(如音频处理、传感器采样)中反复调用,会显著拖慢吞吐量,甚至引发 GC 压力。

解决方案是「缓存 class 引用和方法 ID」,但必须注意生命周期管理:

立即学习Java免费学习笔记(深入)”;

  • 使用 GetObjectClassFindClass + NewGlobalRef 缓存 jclass(否则类卸载后引用失效)
  • GetMethodID/GetFieldID 结果是纯数值,可直接缓存为 static const,无需 global ref
  • 缓存时机放在 JNI_OnLoad 或首次调用时(加锁保护),而非每次函数入口
  • 避免缓存 java.lang.String 等系统类——它们不会被卸载,FindClass 结果可直接复用,但加 NewGlobalRef 更稳妥
static jclass g_StringClass = nullptr;
static jmethodID g_StringInit = nullptr;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM vm, void reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass localClass = env->FindClass("java/lang/String"); if (localClass == nullptr) return JNI_ERR; g_StringClass = reinterpret_cast(env->NewGlobalRef(localClass)); g_StringInit = env->GetMethodID(g_StringClass, "", "([BLjava/nio/charset/Charset;)V"); env->DeleteLocalRef(localClass); return JNI_VERSION_1_6; }

如何用 GetDirectBufferAddress 替代 GetByteArrayElements 避免内存拷贝?

当 Java 层传入 ByteBuffer.allocateDirect() 时,底层是 native 内存;而 GetByteArrayElements 总是触发 JVM 内部拷贝(即使传的是 direct buffer),在图像处理、音视频编解码等大数据量场景下,每帧多一次 memcpy 就是几十 MB/s 的浪费。

Text-To-Song
Text-To-Song

免费的实时语音转换器和调制器

下载

关键判断逻辑:只对 direct buffer 使用 GetDirectBufferAddress,且必须配合 GetDirectBufferCapacity 校验长度。普通 heap byte array 仍需走 GetByteArrayRegionGetByteArrayElements(后者需记得 ReleaseByteArrayElements)。

  • IsDirect 判断是否为 direct buffer
  • GetDirectBufferAddress 返回 void*,不增加引用计数,无需释放
  • 切勿对非 direct buffer 调用该函数——返回 nullptr,解引用即 crash
  • Android 8.0+ 上,direct buffer 的地址可安全用于 DMA 或 GPU 映射(如 Vulkan VK_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_BIT_ANDROID
JNIEXPORT void JNICALL Java_com_example_NativeLib_processDirectBuffer(JNIEnv* env, jobject thiz, jobject buffer) {
    if (!env->IsDirect(buffer)) {
        jclass ex = env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(ex, "buffer must be a direct ByteBuffer");
        return;
    }
    void* addr = env->GetDirectBufferAddress(buffer);
    jlong capacity = env->GetDirectBufferCapacity(buffer);
    if (addr == nullptr || capacity <= 0) return;
    // 直接操作 addr,无拷贝
    process_image_data(static_cast(addr), static_cast(capacity));
}

NDK 中启用 -O3 -flto -march=armv8-a+simd 后为何部分设备闪退?

高级优化标志能提升浮点密集型计算(如 FFT、卷积)30%+ 性能,但会破坏 ABI 兼容性:例如 -march=armv8-a+simd 生成的指令在不支持 NEON 的旧设备(如部分 ARM Cortex-A7)上非法,直接 SIGILL;-flto 可能导致符号重排,与 Java 层 native 方法名映射失败(UnsatisfiedLinkError)。

安全做法是分档构建 + 运行时检测:

  • ABI 分离:为 arm64-v8a 启用 SIMD,armeabi-v7a 仅用 -O2 + -mfpu=neon
  • 运行时 CPU 特性检测:用 android_getCpuFeatures()(需 android/ndk-version.h)判断是否支持 ANDROID_CPU_ARM_FEATURE_NEON,再 dispatch 到不同实现
  • -flto 必须配套 full LTO 链接(CMake 中设 set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)),否则函数内联不生效,还可能因弱符号问题导致崩溃
  • 禁用 -fstack-protector-strong 在某些低端芯片上引发栈对齐异常(尤其 inline asm 场景)

最易忽略的一点:所有 JNI 函数签名必须声明为 extern "C",否则 -flto 可能因 C++ name mangling 导致 Java 找不到 native 方法。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

834

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

739

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

735

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

27

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.6万人学习

C# 教程
C# 教程

共94课时 | 6.9万人学习

Java 教程
Java 教程

共578课时 | 46.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号