如何将C++对象生命周期与Java对象绑定(JNI场景)
问题
我有一个包含C对象的库,正在通过JNI和Java结合使用。理想状态下,我希望C对象的生命周期和对应的Java对象绑定,由Java来控制。需要注意的是应用是多线程架构,C++对象会被多线程访问,且这些对象初始化后不会被修改。
当前我的C++实现如下:
struct Tree { int age = 0; std::string type; }; typedef Tree* TreeHandle; /* * Class: com_example_Tree * Method: initialize * Signature: ()J */ JNIEXPORT jlong JNICALL Java_com_example_Tree_initialize (JNIEnv * env, jclass cls) { auto tree_ptr = new Tree(); return (jlong) tree_ptr; } /* * Class: com_example_Tree * Method: dispose * Signature: (J)V */ JNIEXPORT void JNICALL Java_com_example_Tree_dispose (JNIEnv * env, jclass cls, jlong j_tree_handle) { auto tree_ptr = reinterpret_cast<Tree*>(j_tree_handle); delete tree_ptr; }
Java部分实现如下:
class Tree implements AutoClosable { static { System.load("/path/to/tree.so"); } private long handle; public Tree() { this.handle = initialize(); } @Override protected void finalize() { if (this.handle != 0) { this.close(); } } @Override public synchronized void close() { dispose(this.handle); this.handle = 0; } private static native long initialize(); private static native void dispose(long handle); }
但当前实现存在问题:内存中有一些只能通过C风格指针访问的全局对象。有没有更现代化的实现方式?比如使用智能指针,或者直接将C++对象设计为Java类的成员?
优化方案
1. 用智能指针管理C++对象(包括全局对象)
直接用裸指针管理全局对象容易出现内存泄漏或生命周期混乱,改用std::shared_ptr可以自动维护引用计数,适配多线程环境下的资源管理:
#include <memory> #include <mutex> struct Tree { int age = 0; std::string type; }; // 全局对象用shared_ptr包裹,确保生命周期可控 std::shared_ptr<Tree> global_tree; std::once_flag global_init_flag; // 全局对象初始化(确保只执行一次) void init_global_tree() { global_tree = std::make_shared<Tree>(); global_tree->age = 10; global_tree->type = "Oak"; } // 获取全局对象的智能指针拷贝地址(绑定Java对象生命周期) JNIEXPORT jlong JNICALL Java_com_example_Tree_getGlobalTree (JNIEnv * env, jclass cls) { std::call_once(global_init_flag, init_global_tree); // 将shared_ptr的拷贝分配到堆上,传递其地址给Java auto* shared_ptr_copy = new std::shared_ptr<Tree>(global_tree); return reinterpret_cast<jlong>(shared_ptr_copy); } // 释放Java持有的shared_ptr拷贝 JNIEXPORT void JNICALL Java_com_example_Tree_disposeSharedPtr (JNIEnv * env, jclass cls, jlong ptr) { auto* shared_ptr = reinterpret_cast<std::shared_ptr<Tree>*>(ptr); delete shared_ptr; // 引用计数自动减1,最后一个拷贝释放时全局对象才会销毁 } // 创建新的Tree对象(非全局) JNIEXPORT jlong JNICALL Java_com_example_Tree_initialize (JNIEnv * env, jclass cls) { auto* shared_ptr = new std::shared_ptr<Tree>(std::make_shared<Tree>()); return reinterpret_cast<jlong>(shared_ptr); }
2. 改进Java侧的生命周期绑定逻辑
finalize()已被标记为过时且不可靠,改用PhantomReference+后台清理线程来替代,同时保留AutoClosable让用户主动释放资源:
import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; class Tree implements AutoClosable { private static final ReferenceQueue<Tree> CLEANUP_QUEUE = new ReferenceQueue<>(); private static final ConcurrentMap<Reference<?>, Long> HANDLE_MAP = new ConcurrentHashMap<>(); static { System.load("/path/to/tree.so"); // 后台守护线程处理资源回收 Thread cleanupThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { Reference<?> ref = CLEANUP_QUEUE.remove(); Long handle = HANDLE_MAP.remove(ref); if (handle != null && handle != 0) { disposeSharedPtr(handle); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); cleanupThread.setDaemon(true); cleanupThread.start(); } private long handle; private final PhantomReference<Tree> phantomRef; // 创建新对象 public Tree() { this.handle = initialize(); this.phantomRef = new PhantomReference<>(this, CLEANUP_QUEUE); HANDLE_MAP.put(phantomRef, this.handle); } // 获取全局对象 public static Tree getGlobal() { Tree tree = new Tree(); tree.handle = getGlobalTree(); return tree; } @Override public synchronized void close() { if (this.handle != 0) { disposeSharedPtr(this.handle); HANDLE_MAP.remove(this.phantomRef); this.handle = 0; } } private static native long initialize(); private static native long getGlobalTree(); private static native void disposeSharedPtr(long handle); }
3. 多线程安全保障
由于C++对象初始化后只读,多线程访问本身是线程安全的,无需额外同步锁。如果涉及全局对象初始化,用std::once_flag确保初始化操作仅执行一次,避免竞态条件。
4. 关键注意事项
- 不要尝试直接将C++对象作为Java类成员,Java无法直接持有非Java对象实例,必须通过句柄(指针地址)间接管理。
- 避免直接传递裸指针,用智能指针的堆地址传递,确保资源能被正确回收。
- 优先让用户主动调用
close()释放资源,PhantomReference仅作为兜底方案。
内容的提问来源于stack exchange,提问作者Green 绿色
相关产品推荐
相关产品推荐

