【问题标题】:JNI Object PointersJNI 对象指针
【发布时间】: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&lt;Point**&gt;(obj);(macOS 10.14.6,openjdk 1.8.0_66-b17 64 位)在 C++ 中重现这个
  • 对于jobject 引用的对象内部的内容,您的信息完全为零。因此,使用mem::transmute() 只是一种肆无忌惮的乐观主义。
  • @user207421 我也这么认为,但 OpenJDK 的实现相对容易理解:jobject(搜索jobject_refJNIHandles::resolve_impl)实际上是指向oop 的指针对象,其中包含指向oopDesc 对象的指针,该对象又包含类指针、GC 信息和对象字段。
  • @Botje 这些都没有被指定为 ABI 或在此代码中实现。
  • @Botje 根据您自己的评论,它至少需要三个间接:jobject-&gt;oop-&gt;oopDesc-&gt;{class pointer, GC information, object fields}。这意味着jobject 在任何有用的意义上都不是 Java 对象。

标签: java memory-management rust java-native-interface


【解决方案1】:

概念说明

在阅读了关于内存压缩的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 与测试对象中的相同,并且所有三个标头都与 jol 对内存格式的预测一致(如果我使用有符号整数,我假设标头 3 会相同)。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 1970-01-01
    • 2023-02-09
    • 1970-01-01
    • 2023-04-10
    相关资源
    最近更新 更多