76

目前,我正在尝试创建一个使用 CUDA 功能的 Java 应用程序。CUDA 和 Java 之间的连接工作正常,但我还有另一个问题,想问一下,我的想法是否正确。

当我从 Java 调用本机函数时,我将一些数据传递给它,这些函数会计算一些东西并返回一个结果。是否有可能让第一个函数返回对此结果的引用(指针),我可以将其传递给 JNI 并调用另一个对结果进行进一步计算的函数?

我的想法是通过将数据保留在 GPU 内存中并仅传递对它的引用以便其他函数可以使用它来减少将数据复制到 GPU 和从 GPU 复制数据的开销。

在尝试了一段时间后,我自己想,这应该是不可能的,因为指针在应用程序结束后被删除(在这种情况下,当 C 函数终止时)。它是否正确?还是我只是在 C 语言中很糟糕才能看到解决方案?

编辑:好吧,稍微扩展一下问题(或者说得更清楚):函数结束时,JNI 本机函数分配的内存是否被释放?或者我仍然可以访问它,直到 JNI 应用程序结束或我手动释放它?

感谢您的输入 :)

4

7 回答 7

50

我使用了以下方法:

在您的 JNI 代码中,创建一个包含对您需要的对象的引用的结构。首次创建此结构时,将其指向 java 的指针作为long. 然后,在 java 中,您只需使用 thislong作为参数调用任何方法,并在 C 中将其转换为指向您的结构的指针。

该结构将在堆中,因此在不同的 JNI 调用之间不会被清除。

编辑:我不认为你可以使用 long ptr =(long)&address;因为地址是一个静态变量。按照 Gunslinger47 建议的方式使用它,即创建类或结构的新实例(使用 new 或 malloc)并传递其指针。

于 2009-10-27T23:30:43.023 回答
17

在 C++ 中,您可以使用任何您想要分配/释放内存的机制:堆栈、malloc/free、new/delete 或任何其他自定义实现。唯一的要求是,如果您使用一种机制分配一块内存,则必须使用相同的机制释放它,因此您不能调用free堆栈变量,也不能调用deleteedmalloc内存。

JNI 有自己的分配/释放 JVM 内存的机制:

  • 新对象/删除本地引用
  • 新建全局引用/删除全局引用
  • NewWeakGlobalRef/删除WeakGlobalRef

它们遵循相同的规则,唯一的问题是当本地方法退出时,可以显式地、使用PopLocalFrame或隐式地“整体”删除本地引用。

JNI 不知道您是如何分配内存的,因此当您的函数退出时它无法释放它。堆栈变量显然会被破坏,因为您仍在编写 C++,但您的 GPU 内存将保持有效。

那么唯一的问题是如何在后续调用中访问内存,然后您可以使用 Gunslinger47 的建议:

JNIEXPORT jlong JNICALL Java_MyJavaClass_Function1() {
    MyClass* pObject = new MyClass(...);
    return (long)pObject;
}

JNIEXPORT void JNICALL Java_MyJavaClass_Function2(jlong lp) {
    MyClass* pObject = (MyClass*)lp;
    ...
}
于 2010-03-19T12:42:30.097 回答
11

Java 不知道如何处理指针,但它应该能够存储来自本机函数返回值的指针,然后将其交给另一个本机函数来处理。C 指针只不过是核心的数值。

另一位参与者必须告诉您在 JNI 调用之间是否会清除指向的图形内存以及是否有任何解决方法。

于 2009-10-27T18:00:04.287 回答
11

虽然@denis-tulskiy 接受的答案确实有道理,但我个人已经遵循了这里的建议。

因此,不要使用伪指针类型jlong(或者jint如果您想在 32 位拱门上节省一些空间),而是使用ByteBuffer. 例如:

MyNativeStruct* data; // Initialized elsewhere.
jobject bb = (*env)->NewDirectByteBuffer(env, (void*) data, sizeof(MyNativeStruct));

您可以稍后重新使用它:

jobject bb; // Initialized elsewhere.
MyNativeStruct* data = (MyNativeStruct*) (*env)->GetDirectBufferAddress(env, bb);

对于非常简单的情况,此解决方案非常易于使用。假设你有:

struct {
  int exampleInt;
  short exampleShort;
} MyNativeStruct;

在 Java 方面,您只需要执行以下操作:

public int getExampleInt() {
  return bb.getInt(0);
}

public short getExampleShort() {
  return bb.getShort(4);
}

这使您免于编写大量样板代码!但是,应注意此处解释的字节顺序。

于 2015-04-17T09:38:08.160 回答
9

我知道这个问题已经正式回答,但我想添加我的解决方案:与其尝试传递指针,不如将指针放入 Java 数组(位于索引 0 处)并将其传递给 JNI。GetIntArrayRegionJNI 代码可以使用/获取和设置数组元素SetIntArrayRegion

在我的代码中,我需要本地层来管理文件描述符(打开的套接字)。Java 类拥有一个int[1]数组并将其传递给本机函数。本机函数可以用它做任何事情(get/set)并将结果放回数组中。

于 2011-11-28T08:18:35.753 回答
7

如果您在本机函数内部动态(在堆上)分配内存,它不会被删除。换句话说,您可以使用指针、静态变量等在对本机函数的不同调用之间保留状态。

换一种方式想一想:在另一个 C++ 程序调用的函数调用中,你可以安全地做些什么?同样的事情也适用于这里。当一个函数退出时,该函数调用堆栈上的任何东西都会被销毁;但是除非您明确删除它,否则堆上的任何内容都会保留。

简短的回答:只要您不取消分配要返回给调用函数的结果,它将保持有效,以便以后重新进入。只要确保在完成后将其清理干净。

于 2009-10-27T22:19:33.680 回答
1

最好完全按照 Unsafe.allocateMemory 的方式执行此操作。

创建您的对象,然后将其键入 (uintptr_t) 这是一个 32/64 位无符号整数。

return (uintptr_t) malloc(50);

void * f = (uintptr_t) jlong;

这是唯一正确的方法。

这是 Unsafe.allocateMemory 所做的健全性检查。

inline jlong addr_to_java(void* p) {
  assert(p == (void*)(uintptr_t)p, "must not be odd high bits");
  return (uintptr_t)p;
}

UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
  UnsafeWrapper("Unsafe_AllocateMemory");
  size_t sz = (size_t)size;
  if (sz != (julong)size || size < 0) {
    THROW_0(vmSymbols::java_lang_IllegalArgumentException());
  }
  if (sz == 0) {
    return 0;
  }
  sz = round_to(sz, HeapWordSize);
  void* x = os::malloc(sz, mtInternal);
  if (x == NULL) {
    THROW_0(vmSymbols::java_lang_OutOfMemoryError());
  }
  //Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
  return addr_to_java(x);
UNSAFE_END
于 2015-08-19T13:30:35.970 回答