jni如何判断两个jobject是否为同一个java对象

本文详细介绍了在JNI开发中如何使用全局引用管理和比较Java对象。通过实例演示了如何判断两个jobject是否指向同一个Java对象,以及如何正确管理全局引用避免内存泄漏。

  jni如何判断两个jobject是否为同一个java对象

 

    jni开发时有时候需要将java对象缓存到native层,方便native层通过jni的反射方法进行回调操作。通常我们会将回调接口callback在native层存放为global reference全局引用,熟悉jni开发的都知道,jni传入到native 层的jobject生命周期仅仅是函数的生命周期,当jni函数返回后对应的jobject对象就会失效,不能再操作,所以就必须申请为global reference。

NewGlobalRef 将变量申请为全局引用,此时java虚拟机会保留jobject所指向的对象防止被垃圾回收器回收。 DeleteGlobalRef 释放全局引用,允许java虚拟机回收该引用指向的java对象,对于不需要使用的global reference必须调用该方法,否则会引起java虚拟机内存泄漏。

      既然global reference的实质是指向java对象,那么我们在将某个变量声明为global reference时如何判断该jobject是否已经是当前设置的global reference呢。经过一番查询,看到了一下方法

IsSameObject 如果两个jobject指向同一个java 对象那么返回true,否则返回false。

 

既然有方法了,接下来就是验证工作了。

Hello.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    native_setObjectToGlobalRef
 * Signature: (LHello/Book;)V
 */
JNIEXPORT void JNICALL Java_Hello_native_1setObjectToGlobalRef
  (JNIEnv *, jclass, jobject);

/*
 * Class:     Hello
 * Method:    native_release
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_native_1release
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

Hello.cpp

#ifdef __cplusplus
extern "C" {
#endif

#include <cstdio>
#include "Hello.h"

// 全局变量,用于存储全局引用的
jobject g_book_ref = NULL; 

/*
 * Class:     Hello
 * Method:    native_setObjectToGlobalRef
 * Signature: (LHello/Book;)V
 */
JNIEXPORT void JNICALL Java_Hello_native_1setObjectToGlobalRef
  (JNIEnv *env, jclass jclazz, jobject jbook) {

	// 如果输入参数为null,那么不需要继续操作
	if (NULL == jbook) {
		printf("native_setObjectToGlobalRef jbook is NUll and g_book_ref is NOT NULL. we need to delete reference of old g_book_ref\n");
		if (NULL != g_book_ref) {
            env->DeleteGlobalRef(g_book_ref);  // 删除老的全局引用
		    g_book_ref = NULL;
        }
		return;
	}

   // 如果全局引用已经设置了,那么需要判断jni函数参数的jbook是否与当前的全局引用指向同一个java对象
   if (NULL != g_book_ref) {
        // 如果指向同一个对象,那么不需要再次为该jbook申请全局引用
		if (env->IsSameObject(g_book_ref, jbook)) {
			printf("native_setObjectToGlobalRef isSameObject: true, g_book_ref: %p, jbook: %p\n", g_book_ref, jbook);
		} else {
            // 如果指向不同对象,那么先释放老的全局引用,再为jbook申请全局引用
			printf("native_setObjectToGlobalRef isSameObject: false\n");
			printf("native_setObjectToGlobalRef g_book_ref and jbook is not same object. we need to delete reference of old g_book_ref\n");
			env->DeleteGlobalRef(g_book_ref);
			printf("native_setObjectToGlobalRef create global reference to g_book_ref\n");
			g_book_ref = env->NewGlobalRef(jbook);
		}
	} else {
		printf("native_setObjectToGlobalRef g_book_ref is NULL\n");
		printf("native_setObjectToGlobalRef create global reference to g_book_ref\n");
		g_book_ref = env->NewGlobalRef(jbook);
	}
}

/*
 * Class:     Hello
 * Method:    native_release
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_native_1release
  (JNIEnv *env, jclass jclazz) {
    printf("native_release g_book_ref: %p\n", g_book_ref);
	if (NULL != g_book_ref) {
		env->DeleteGlobalRef(g_book_ref);
		g_book_ref = NULL;
	}
}


#ifdef __cplusplus
}
#endif


Hello.java

public class Hello {
	static {
		try {
			String sysName = System.getProperty("os.name");
			if (sysName.contains("Linux")) {
		        System.load(System.getProperty("user.dir") + "/libHello.so");
			} else if (sysName.contains("Drawin") || sysName.contains("Mac")) {
		        System.load(System.getProperty("user.dir") + "/libHello.dylib");
			}
		} catch (java.lang.UnsatisfiedLinkError e) {
			e.printStackTrace();
			System.err.println("load so failed.");
			System.exit(1);
		}
	}

	static class Book {
		public String name;

		public Book() {}
		public Book(String name) {
			this.name = name;
		}
	}

	public native static void native_setObjectToGlobalRef(Book book);
	public native static void native_release();

	public static void main(String args[]) {
		Book b1 = new Book();
		Book b2 = new Book();
		System.out.println("set b1");
		native_setObjectToGlobalRef(b1);
		System.out.println("set b2");
		native_setObjectToGlobalRef(b2);
		System.out.println("##set b2");
		native_setObjectToGlobalRef(b2);
		System.out.println("##set null");
		native_setObjectToGlobalRef(null);
		native_release();
	}
}

 

makefile

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
main:
	gcc -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/darwin" Hello.cpp -dynamiclib -o libHello.dylib
endif

ifeq ($(UNAME_S),Linux)
main:
	gcc -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux" Hello.cpp -shared -fPIC -o libHello.so
endif

 

运行命令

$ make

$ javac Hello.java

 

运行程序

$ java -Djava.library.path=".:${JAVA_HOME}" Hello

输出结果

set b1
native_setObjectToGlobalRef g_book_ref is NULL
native_setObjectToGlobalRef create global reference to g_book_ref
set b2
native_setObjectToGlobalRef isSameObject: false
native_setObjectToGlobalRef g_book_ref and jbook is not same object. we need to delete reference of old g_book_ref
native_setObjectToGlobalRef create global reference to g_book_ref
##set b2
native_setObjectToGlobalRef isSameObject: true, g_book_ref: 0x7fb16ae08e90, jbook: 0x70000ce4a900
##set null
native_setObjectToGlobalRef jbook is NUll and g_book_ref is NOT NULL. we need to delete reference of old g_book_ref
native_release g_book_ref: 0x0

1. set b1时

因为是首次设置,此时native的g_book_ref还是null,没有被指向java对象,所以需要用全局引用为jbook保存java对象引用;

2. set b2时

isSameObject返回false,证明g_book_ref和jbook指向的对象不一样(此时jbook指向b2,而g_book_ref指向步骤1设置的b1),所以需要先删除老的b1全局引用再为b2申请全局引用;

3. 再次设置b2时

isSameObject返回true,证明g_book_ref和jbook指向的对象一样(此时jbook和g_book_ref都指向b2,注意两者的native地址不一样),为了节省操作,所以不需要在为jbook申请全局引用;

4. 设置为null

因为jbook为null,所以需要释放全局引用g_book_ref。

5. native释放

在Book native释放时因为步骤4已经提前释放了全局引用,所以不需要再释放了。

 

 

<think>嗯,用户的问题是关于JNI中如何将C++对象数组返回给Java。这个问题很有技术深度,需要仔细梳理。用户可能正在开发JNI相关的跨语言交互功能,遇到对象数组传递的瓶颈。从ta引用的资料看,已经做过初步研究,但需要具体实现方案。用户真正的需求很明确:如何用C++生成对象数组后通过JNI完整传递到Java层,而不仅仅是基本类型数组。考虑到JNI对象数组的特殊处理方式,核心难点在于两点:一是如何在C++端构造jobjectArray,二是如何封装C++对象Java对象。用户可能是AndroidNDK开发者或Java中间件工程师,正在处理性能敏感的逻辑。ta引用中特别提到“没有直接转换方法”,说明已意识到这是个痛点。情绪上可能有些焦虑,毕竟这类问题容易卡住开发进度。我回忆起JNI确实禁止直接访问Java堆外内存中的对象数组。必须通过SetObjectArrayElement逐个填充元素,但这样会频繁触发JNI调用影响性能。另一种思路是先在C++构造所有对象,再一次性复制到数组里——但这要求对象结构稳定。技术上还需要注意数组维度问题。用户引用的资料[4]提到二维三维数组传递,但当前问题聚焦一维对象数组。考虑到后续扩展性,可以强调多维的处理思路类似。还有个隐形风险:如果C++对象的生命周期比数组短,返回Java后可能悬空指针。必须确保C++对象足够持久,或通过JNI全局引用保护。这个细节容易被忽略但极其关键。最后用户似乎更关注“方法”而非原理,所以回复应该以代码骨架为主,理论为辅。但安全提示不能省略,尤其是全局引用管理这块,否则容易导致内存泄漏。</think> 在JNI中返回C++对象数组到Java需要以下关键步骤(参考JNI规范及引用[1][3][4]): ### 核心实现流程 1. **创建Java对象数组** 在C++端使用`NewObjectArray`创建Java数组: ```c++ jclass clazz = env->FindClass("com/example/MyClass"); // 目标JavajobjectArray result = env->NewObjectArray( // 创建对象数组 arraySize, // 数组长度 clazz, // 元素类型 nullptr // 初始值(设为NULL) ); ``` 2. **封装C++对象Java对象** 通过JNI方法将每个C++对象转为Java对象: ```c++ for (int i = 0; i < arraySize; i++) { jobject javaObj = ConvertToJavaObject(env, cppObj[i]); // 自定义转换函数 env->SetObjectArrayElement( // 填充数组元素 result, i, javaObj ); env->DeleteLocalRef(javaObj); // 释放局部引用(重要!) } ``` 3. **返回数组给Java** 在JNI函数中直接返回创建的对象数组: ```c++ return result; // 返回到Java层 ``` ### 关键细节说明 1. **对象转换要求** - `ConvertToJavaObject`需自行实现(见下文代码模板) - 必须将C++对象数据映射到Java对象的字段/方法 - 通过`env->NewObject`构造Java对象实例 2. **内存管理原则** - ✔️ 对每个创建的Java对象使用`DeleteLocalRef`防止内存泄漏 - ❌ **禁止直接传递C++指针**到Java层(引用[3]) - 使用`SetObjectArrayElement`代替内存拷贝(引用[1][3]) 3. **代码示例** ```c++ // C++到Java对象转换模板 jobject ConvertToJavaObject(JNIEnv* env, CppClass& cppObj) { jclass jcls = env->FindClass("com/example/MyClass"); jmethodID constructor = env->GetMethodID(jcls, "<init>", "()V"); jobject jobj = env->NewObject(jcls, constructor); // 设置字段值(示例) jfieldID fid = env->GetFieldID(jcls, "data", "I"); env->SetIntField(jobj, fid, cppObj.value); return jobj; } ``` ### 性能优化建议 1. **缓存JNI标识符**(避免重复查找) ```c++ // 全局初始化时缓存 static jclass cachedClass = (jclass)env->NewGlobalRef(clazz); static jmethodID cachedConstructor = env->GetMethodID(cachedClass, "<init>", "()V"); ``` 2. **处理多维数组**(引用[4]) 二维数组需嵌套创建: ```c++ jobjectArray jarray = env->NewObjectArray(dim1, env->FindClass("[Ljava/lang/Object;"), NULL); for(int i=0; i<dim1; i++) { jobjectArray inner = env->NewObjectArray(dim2, targetClass, NULL); env->SetObjectArrayElement(jarray, i, inner); } ``` 3. **使用全局引用** 若需长期持有Java对象: ```c++ jobject globalObj = env->NewGlobalRef(localObj); env->DeleteLocalRef(localObj); // 删除局部引用 ``` > **重要安全提示**:必须显式管理JNI引用(局部/全局),否则会导致内存泄漏或JVM崩溃(引用[1][3])。跨线程操作需使用`AttachCurrentThread`获取有效JNIEnv指针[^1][^3][^4]。 --- ### 相关问题 1. 如何在C++中直接修改Java对象数组的元素而不返回整个数组? 2. JNI处理对象数组时如何避免内存泄漏? 3. 多线程环境下返回对象数组需要注意什么? 4. 如何传递包含复杂继承关系的C++对象数组到Java? 5. JNI能否直接操作Java对象数组的底层内存?[^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值