【发布时间】:2020-02-11 16:50:32
【问题描述】:
问题
在尝试使用 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 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 标头中显示的jobject 不同。 JObject 是指向 jobject 的指针周围的 rust 包装器,因此我在转换指针之前取消引用它。
【问题讨论】:
-
您确定您的结构布局与 Java 对象布局匹配吗?我无法使用
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 位)在 C++ 中重现这个 -
对于
jobject引用的对象内部的内容,您的信息完全为零。因此,使用mem::transmute()只是一种肆无忌惮的乐观主义。 -
@user207421 我也这么认为,但 OpenJDK 的实现相对容易理解:
jobject(搜索jobject_ref或JNIHandles::resolve_impl)实际上是指向oop的指针对象,其中包含指向oopDesc对象的指针,该对象又包含类指针、GC 信息和对象字段。 -
@Botje 这些都没有被指定为 ABI 或在此代码中实现。
-
@Botje 根据您自己的评论,它至少需要三个间接:
jobject->oop->oopDesc->{class pointer, GC information, object fields}。这意味着jobject在任何有用的意义上都不是 Java 对象。
标签: java memory-management rust java-native-interface