问题
在试验 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/