java - JNI 对象指针

标签 java memory-management rust java-native-interface

问题

在试验 JNI 接口(interface)时,我想知道是否可以采用 JObject 并将其转换为等效结构来操作字段。然而,当我尝试时,我惊讶地发现这不起作用。 忽略这个想法可能有多可怕,为什么它行不通?


我的方法

Java 测试类

我做了一个简单的类 Point 来做我的测试。 Point 有两个字段和一个接受 x 和 y 的构造函数,以及一些根据字段返回信息的随机方法。

public class Point {
    public final double x;
    public final double y;
    // As well as some random methods
}

内存布局

使用 jol,我在 java 运行时中找到了我的 Point 类的结构(如下所示)。

C:\Users\home\IdeaProjects\test-project>java -cp jol-cli-0.9-full.jar;out\production\java-test org.openjdk.jol.Main internals Point
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Instantiated the sample instance via public Point(double,double)

Point object internals:
 OFFSET  SIZE     TYPE DESCRIPTION                               VALUE
      0     4          (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4          (object header)                           31 32 01 f8 (00110001 00110010 00000001 11111000) (-134139343)
     12     4          (alignment/padding gap)
     16     8   double Point.x                                   0.0
     24     8   double Point.y                                   0.0
Instance size: 32 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

测试结构

我编写了一个简单的测试结构来匹配 jol 描述的内存模型以及一些测试以确保它具有相同的对齐方式并且每个元素都有正确的偏移量。我使用 Rust 完成了此操作,但它对于任何其他编译语言都应该是相同的。

#[derive(Debug)]
#[repr(C, align(8))]
pub struct Point {
    header1: u32,
    header2: u32,
    header3: u32,
    point_x: f64,
    point_y: f64,
}

输出

我测试的最后一部分是创建一个 jni 函数,该函数接收 Point 对象并将点对象转换为点结构。

C 头文件(作为引用包含在内)

/*
 * Class:     Main
 * Method:    analyze
 * Signature: (LPoint;)V
 */
JNIEXPORT void JNICALL Java_Main_analyze
  (JNIEnv *, jclass, jobject);

Rust 实现

#[no_mangle]
pub extern "system" fn Java_Main_analyze(env: JNIEnv, class: JClass, obj: JObject) {
    unsafe {
        // De-reference the `JObject` to get the object pointer, then transmute the
        // pointer into my `Point` struct.
        let obj_ptr = mem::transmute::<_, *const Point>(*obj);

        // Output the debug format of the `Point` struct
        println!("{:?}", *obj_ptr);
    }
}

运行

每次我运行它,我都会得到不同的结果。

// First Run:
Point { header1: 1802087032, header2: 7, header3: 43906792, point_x: 
0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000230641669, point_y: 
0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000021692881 }

// Second Run:
Point { header1: 1802087832, header2: 7, header3: 42529864, point_x: 
0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000229832192, point_y: 
0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000021012588 }

版本信息

C:\Users\home\IdeaProjects\test-project>java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

编辑:我在 Windows 10 Home 10.0.18362 Build 18362

上执行此操作

编辑 2: 因为我使用 rust 来解决这个问题,所以我使用了 rust 的 jni crate .它提供了我上面提到的 JObject 类型。我只是想到可能会有一些混淆,因为 JObject 与 C header 中显示的 jobject 不同。 JObject 是指向 jobject 指针的 Rust 包装器,因此我在转换指针之前取消引用它。

最佳答案

概念解释

看完paper关于内存压缩,我了解到 java 引用由两个指针组成。 在垃圾收集期间,会发生压缩步骤。为了确保引用仍然对齐,使用了两个指针来防止移动对象时发生重整。引用由指向另一个指向对象的指针的指针组成。当压缩步骤发生并且对象在内存中移动时,只需要更改第二个指针。

换句话说,引用实际上是指向内存中指向对象的位置的指针。

我知道我弄错了那个解释,但希望它基本上是可读的/准确的。

我做错了什么

代码已更改

#[no_mangle]
pub extern "system" fn Java_Main_analyze(env: JNIEnv, class: JClass, obj: JObject) {
    unsafe {
        // It should have been transmuted to a pointer to a pointer and dereferenced twice.
        let indirect = mem::transmute::<_, *const *const Point>(*obj);
        println!("{:?}", **indirect);
    }
}

新输出

应用该修复程序后,对象数据开始正确排列。 x 和 y 与测试对象中的相同,并且所有三个 header 都与 jol 对内存格式的预测一致(我假设如果我使用有符号整数, header 3 将是相同的)。

Point { header1: 1, header2: 0, header3: 4160799044, point_x: -3.472, point_y: 4.0 }

回应@Botje:我的 Point 结构是正确的,但你无法重现错误,因为你从一开始就正确地解决了问题,而我没有.

关于java - JNI 对象指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58387338/

相关文章:

java - 如何在不完成/停止子 Activity 的情况下完成子 Activity 的父 Activity ?

java - 设置 gridview 中第一个可见项目

java - "kill -QUIT"是否真的杀死了 JVM?

c++ - 如何防止递归函数中不必要的内存使用

rust - 添加引用和数字值时了解(自动?)取消引用/强制

java - 如何在 AlertDialog 之上举起 Toast?

iphone - @property setter 泄漏

mysql - 我可以告诉Mysql哪些索引保留在内存中而不管其他索引吗?

rust - 我如何访问 Rust 目录中的文件?

rust - 如何从现在算起 5 分钟?