You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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 绿色

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 07:25:22