【问题标题】:How exactly is memory allocation taking place and how are Java and C interacting to keep track of the same object?内存分配究竟是如何发生的,Java 和 C 如何交互以跟踪同一个对象?
【发布时间】:2019-09-24 07:12:42
【问题描述】:

我有一个包含 long 类型的成员变量和一组本地方法声明的类。我相信变量的内存是在一个本机方法中分配的,并且通过调用另一个本机方法销毁在 finalize() 方法中尝试取消分配。我知道 finalize 已被弃用,但在找到 finalize 的替代方案之前,我需要了解内存分配是如何发生的,Java 和 C 如何通过 JNI 保持同步以及如何跟踪这个特定的成员变量。我将尝试使用代码 sn-ps 更好地解释该场景:

JSQ.java

class JSQ {
    protected long shdl;
    protected JSQ() { log("JSQ constructor"); }
    protected JSQ(String stmt, boolean isd)
        throws DhSQLException
    {
        // calls a set of native methods
        set_shdl();
        setstmt(stmt, shdl);
        setd(isd, shdl);
        prep(shdl);
    }

    public void finalize()
    {
        destroy(shdl);
    }

    private native void set_shdl() throws DhSQLException;
    private native void setstmt(String s, long shdl) throws DhSQLException;
    private native void setd(boolean b, long shdl);
    private native void prep(long shdl) throws DhSQLException;
    private native void destroy(long shdl);

    protected void execute() throws DhSQLException
    {
        parExec(shdl);
    }

    protected native void parExec(long shdl);
}

JSQL.cxx

#define SQ ((sq_stsm_t *)(shdl))

JNIEXPORT void JNICALL Java_com_project_package_JSQ_set_shdl
(JNIEnv *env, jobject obj_This)
{
    jclass cls;
    jmethodID mid;
    cls = (env)->GetObjectClass (obj_This);
    mid = (env)->GetMethodID (hThis,"set_JSQ_shdl","(J)V");

    status_t status;
    // memory allocation
    sq_stsm_t * S = new sq_stsm_t(status);
    if(status)
    {
        if (S) { delete S; }
        return;
    }
    // I understand that we're attempting to call a Java method from a native method.
    // But which method is it calling?
    // Also, are we converting S from sq_stms_t type to jlong?
    (env)->CallVoidMethod (obj_This,mid,(jlong) S);
    return;
}

JNIEXPORT void JNICALL Java_com_project_package_JSQ_setstmt
(JNIEnv *env, jobject, jstring jstmt, jlong shdl)
{
    status_t status;

    // cstmt is obtained using jstmt

    // Note: #define SQ ((sq_stsm_t *)(shdl))
    status = SQ->SetStmt(cstmt);

    return;
}

JNIEXPORT void JNICALL Java_com_project_package_JSQ_destroy
(JNIEnv *, jobject, jlong shdl)
{
    delete SQ;
}

我感到困惑的地方:

  1. 为什么 long 和 jlong​​ 以及从用户定义类型 sq_stsm_t 到 long 的转换。我知道 Java 不知道这种用户定义的类型。但是为什么选择long呢?

  2. 为什么 SQ 上的#define 以及destroy 究竟如何通过在SQ 上调用delete 来删除set_shdl 中分配的内存?

发布此消息后,我必须找到 finalize 的替代品 - 为此我得出结论,到目前为止,带有 try-with-resources 的 AutoCloseable 是我的最佳选择。由于涉及到很多本地调用,我对如何处理它感到困惑。但是,这是一个需要解决的单独问题,我不想在这里讨论。只是提到在用例上设置一些背景。

【问题讨论】:

    标签: java c++ java-native-interface java-11


    【解决方案1】:

    Java long 变量保证为 64 位宽,因此包含地址就足够了,即使在 64 位系统上也是如此。 long 的转换只是为了让 C++ 类型系统满意。 如您所见,SQ 宏用于将此地址转换回指向sq_stsm_t 对象的正确指针。

    至于您的第二个问题:宏所做的只是摆脱一些打字工作。 程序的 Java 部分将始终传递 long 类型的 shdl 变量,因此 SQ 宏只是提供对实际 sq_stsm_t 对象的轻松访问。

    最后,C++ 中的newdelete 一起出现:new 分配内存并调用构造函数,delete 调用析构函数并再次释放内存。 请注意,“免费”并不一定意味着“还给操作系统”,因此您的进程的内存使用量在此delete 操作之后可以保持不变。

    至于您代码中的注释:Java_com_project_package_JSQ_set_shdl 尝试调用 Java 方法 boolean com.project.package.JSQ#set_JSQ_shdl(jlong),尽管它不存在于您粘贴的代码中。我猜这是shdl 字段的简单设置器?

    至于您的后续计划:实施AutoCloseable 是个好主意。您需要在 JSQ 对象中创建一个 native void close() 方法,该方法在存储在 shdl 字段中的 sq_stsm_t 对象上调用 delete,然后将 shdl 字段替换为 (jlong)nullptr

    【讨论】:

    • 谢谢。你说的对。本机方法 set_shdl 正在调用我的 JSQ 类中定义的 set_JSQ_shdl 方法。我以前无法弄清楚这一点。代码现在更有意义了。另外,感谢您对 AutoCloseable 的确认。我打算让 JSQ 实现 AutoCloseable 并在其覆盖的 close() 方法中调用 destroy() 。但是,我在这里遇到的另一个问题是,JSQ 类已经声明了 close 方法(本机),该方法由其子类调用,导致编译错误。无法删除现有的本机 close() 方法。有什么建议吗?
    • 您还可以通过从对象中读取shdl 来使界面更苗条,而不必将其作为参数传递。
    • 目前setstmt 和朋友采用long shdl 参数。您可以删除该参数,而是使用 env->GetLongField 从 Java 对象中获取 shdl
    • 至于您的其他评论:如果close 在概念上与destroy 相同,那么问题是什么?
    • 那很不幸。恐怕你必须改变你的 API 才能工作。
    猜你喜欢
    • 1970-01-01
    • 2012-06-10
    • 2019-05-03
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 2013-02-15
    • 2015-09-04
    相关资源
    最近更新 更多