JNI对象指针

8

问题

在尝试使用JNI接口时,我想知道是否可以将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头文件中显示的jobject不同。JObject是一个指向jobject的指针的Rust包装器,因此我在转换指针之前对其进行了取消引用。


1
你确定你的结构体布局绝对匹配 Java 对象布局吗?我无法在 C++ 中重现这个问题,使用 struct Point { uint32_t hdr[3]; double x, y; };Point* point = *reinterpret_cast<Point**>(obj);(macOS 10.14.6,openjdk 1.8.0_66-b17 64 位)。 - Botje
2
你完全不知道jobject所指向的对象内部是什么。因此,使用mem::transmute()只是一种无限乐观的做法。 - user207421
1
@Botje 这些都没有被指定为ABI,也没有在这段代码中实现。 - user207421
1
根据您自己的评论,它至少需要三个间接引用:jobject->oop->oopDesc->{类指针、GC信息、对象字段}。这意味着jobject在任何有用的意义上都不是Java对象。 - user207421
@user207421 完全正确。这是一次盲目乐观和对JNI工作原理的好奇尝试。当这个尝试不可避免地失败并且我的初始在线搜索没有产生任何有用的结果时,我没有想到去查看OpenJDK,所以我来到了Stack Overflow,试图学习我应该做些什么不同的事情。 - Locke
显示剩余2条评论
1个回答

6

概念解释

在阅读有关内存压缩的论文后,我了解到 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与测试对象相同,并且所有三个标题与jol对于内存格式的预测相符(如果我使用有符号整数,我认为标题3也会相同)。

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

回复 @Botje:我的Point结构体是正确的,但你能够成功重现错误是因为你从一开始就正确地解决了这个问题,而我没有。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接