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已经提前释放了全局引用,所以不需要再释放了。