JNI Object 指针

JNI Object Pointers

问题

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


我的方法

Java测试Class

我做了一个简单的 class Point 来做我的测试。 Point 有两个字段和一个接受 x 和 y 的构造函数以及一些 运行dom 方法,这些方法 return 基于字段的信息。

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

内存布局

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

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 object 和 t运行 将点 object 模糊到点结构中。

C Header(作为参考)

/*
 * 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 包装器,因此我在 t运行 修改指针之前取消引用它。

概念解释

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

换句话说,引用实际上是指向内存中指向 object.

的位置的指针

我知道我搞砸了那个解释,但希望它主要是 readable/accurate。

我做错了什么

代码已更改

#[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);
    }
}

新输出

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

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

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