Указатели объектов JNI - PullRequest
       112

Указатели объектов JNI

7 голосов
/ 15 октября 2019

Проблема

Когда я экспериментировал с интерфейсом 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Я обнаружил структуру моего Point класса во время выполнения Java (показано ниже).

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: Поскольку для решения этой проблемы я использовал ржавчину, я использовал ржавчину jni ящик . Это обеспечило JObject тип, который я упомянул выше. Мне только что пришло в голову, что может быть некоторая путаница, поскольку JObject не совпадает с jobject, показанным в заголовке C. JObject - это оболочка от ржавчины вокруг указателя на jobject, поэтому я разыменую его перед преобразованием указателя.

1 Ответ

4 голосов
/ 15 октября 2019

Концептуальное объяснение

Прочитав статью о сжатии памяти, я узнал, что ссылки на 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 была правильной, но вы не смогли воссоздать ошибку, потому что вы правильно подошли к проблеме с самого начала, а я - нет.

...