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
结构是正确的,但你无法重现错误,因为你从一开始就正确地解决了问题,而我没有。
问题
在试验 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
结构是正确的,但你无法重现错误,因为你从一开始就正确地解决了问题,而我没有。