【问题标题】:How is memory freed when native libraries are called from Java and Python?从 Java 和 Python 调用本机库时如何释放内存?
【发布时间】:2014-02-11 01:19:03
【问题描述】:

我正在为 OpenCV 使用 Java 包装器。我之前也使用过 Python 包装器。我阅读了以下关于 JNI 的内容:

The JNI framework does not provide any automatic garbage collection for non-JVM
memory resources allocated by code executing on the native side.

我很好奇在使用 Java 包装器和 Python 包装器时如何释放本地库动态分配的内存?

【问题讨论】:

  • 本机库和操作系统负责整理JVM之外使用的资源。
  • 人们还在使用 JNI 吗?我认为 JNA 应该会更好。

标签: java python memory-management garbage-collection java-native-interface


【解决方案1】:

JNI 框架不提供自动垃圾收集 外部资源,但包装器为语言提供绑定 会的。

Python 提供自动垃圾回收,但如果您使用 C API 您必须手动获取任何references 并将其释放到 python 对象并管理对本机数据的引用。 Py_INCREF()会 将PyObject * 的引用计数增加一,这样您 可以确保在您致电Py_DECREF() 之前它仍然存在 稍后相同的对象。一旦引用,内存将被释放 计数达到零(是的,这是因为垃圾的简化 收集器和循环引用,但原则上是正确的)。

Java(JVM)也提供自动垃圾收集,但如果你 使用 JNI,您必须手动管理所有 references 到您的 对象。这与 Python 的行为类似,使用 (*env)->DeleteLocalRef() 或 (*env)->Release...()(取决于 对象的类型)释放对获取的对象的本地引用 通过 JNI。

假设你想包装原生 C 库 foo:

// foo.h
typedef struct {
  char * bar;
} foo;

foo * foo_new(const char * bar);
void foo_delete(foo * self);

// foo.c
#include <stdlib.h>
#include <string.h>
#include "foo.h"

foo * foo_new(const char * bar) {
  // Allocate foo.
  foo * self = malloc(sizeof(foo));
  if (self == NULL) {
    return NULL;
  }
  memset(self, 0, sizeof(foo));
  // Copy bar to foo.
  size_t bar_len = strlen(bar);
  char * bar_copy = malloc(sizeof(char) * (bar_len + 1));
  if (bar_copy == NULL) {
    foo_delete(self);
    return NULL;
  }
  strncpy(bar_copy, bar, bar_len);
  bar_copy[bar_len] = '\0';
  self->bar = bar_copy;
  // Return foo pointer.
  return self;
}

void foo_delete(foo * self) {
  if (self == NULL) {
    return;
  }
  // Free bar.
  if (self->bar != NULL) {
    free(self->bar);
    self->bar = NULL;
  }
  // Free foo.
  free(self);
}

因此,如果您要编写 Python 绑定到本机 foo 库,您会 需要一个包装 Python 对象,它存储指向本机数据的指针 包装器将管理的。当 Python 包装器对象是 销毁,它必须释放任何指向本机数据的指针,并且 释放对 Python 对象的所有引用。

// pyfoo.c
#include <Python.h>
#include "foo.h"

// class PyFoo wraps struct foo.
typedef struct {
  PyObject_HEAD;
  foo * foo;
} PyFoo;

static PyObject * PyFoo_new(PyTypeObject * type, PyObject * args, PyObject * kwargs) {
  // Parse bar from arguments.
  const char * bar;
  if (!PyArg_ParseTuple(args, "s", &bar)) {
    return NULL;
  }
  // Allocate PyFoo.
  PyFoo * self;
  self = (PyFoo *)type->tp_alloc(type, 0);
  if (self == NULL) {
    return NULL;
  }
  // Create internal foo with bar.
  self->foo = foo_new(bar);
  if (self->foo == NULL) {
    Py_DECREF(self);
    return NULL;
  }
  // Return PyFoo instance.
  return (PyObject *)self;
}

static void PyFoo_dealloc(PyFoo * self) {
  // Delete internal foo pointer.
  foo_delete(self->foo);
  // Free python object.
  self->ob_type->tp_free((PyObject *)self);
}

static PyObject * PyFoo_bar(PyFoo * self) {
  // Return a copy of bar.
  return (PyObject *)PyString_FromString(self->foo->bar)
}

static PyMethodDef PyFoo_methods[] = {
  {"foo", (PyCFunction)PyFoo_bar, METH_NOARGS, "Returns foo."},
  {NULL}, // Sentinel.
};

要在 Java 中包装原生 foo 库,您需要定义包装器 Java 班级。它将使用 JNI 与本机库进行通信,并 存储相关的本机指针(可能是一个通用包装器,它创建一个 每个本机指针的实例)。在类析构函数中(或最接近你的 可以在Java中获取),然后它将使用本机库来释放本机 库指针。

// Foo.java
public class Foo {

  private long foo_ptr;

  public Foo(String bar) {
    // Create internal foo with bar.
    this.foo_ptr = this.foo_new(bar)
  }

  protected void finalize() {
    // Delete internal foo.
    this.foo_delete(this.foo_ptr);
  }

  public String getBar() {
    // Return a copy of bar.
    return this.foo_bar(this.foo_ptr);
  }

  static {
    System.loadLibrary("javafoo");
  }

  private native long foo_new(String bar);
  private native void foo_delete(long foo_ptr);
  private native String foo_bar(long foo);
}

// javafoo.c
#include <jni.h>
#include "foo.h"

JNIEXPORT jlong JNICALL Java_Foo_foo_new(JNIEnv * env, jstring jbar) {
  // Convert java string to c string.
  char * bar = (*env)->GetStringUTFChars(env, jbar, NULL);
  if (bar == NULL) {
    return 0;
  }
  // Create internal foo with bar.
  foo * self = foo_new(bar);
  if (self == NULL) {
    (*env)->ReleaseStringUTFChars(env, jbar, bar);
    return 0;
  }
  // Delete bar c string.
  (*env)->ReleaseStringUTFChars(env, jbar, bar);
  // Return foo pointer as long.
  return (jlong)self;
}

JNIEXPORT void JNICALL Java_Foo_foo_delete(JNIEnv * env, jlong foo_ptr) {
  // Convert foo pointer from long.
  foo * self = (foo *)foo_ptr;
  // Delete internal foo.
  foo_delete(self);
}

JNIEXPORT jstring JNICALL Java_Foo_foo_bar(JNIEnv * env, jlong foo_ptr) {
  // Convert foo pointer from long.
  foo * self = (foo *)foo_ptr;
  // Create java string from bar c string.
  jstring jbar = (*env)->NewStringUTF(env, self->bar);
  // Return bar java string.
  return jbar;
}

注意:这些源示例仅作为简单示例,并没有 已经过测试,可能会省略细节,并且可能不一定遵循已建立的, 最佳做法。

【讨论】:

    【解决方案2】:

    Python 被配置为收集取消引用的 Python 对象。这是通过将PyObject 的引用计数检查为Py_INCREF() 来完成的,这也映射到它计算Java 对象空间的JVM 空间。此过程递减,直到未引用变量的计数达到零。

    我们可以说一旦这个connter达到零,垃圾就被清除了。这个包装对象将这些完整的数据存储到一个 HashMap 中。释放内存后,HashMap 也将用于垃圾收集。这些技术也被 JNI 用于 JVM,这使得它在这个空间中使用起来兼容并灵活。

    【讨论】:

    • 顺便说一句,最近因为安全漏洞,JNI 没有被广泛使用
    • 是什么让 JNI 天生不安全?
    • @cpburnz 有一个很好的paper 。希望这可以帮助您找到您正在寻找的答案。
    • @cpburnz 希望你能得到答案
    • 我通常不会想到缺乏类型安全的不安全性,而是 sql 注入或未经处理的评估。这是一篇有趣的论文。
    猜你喜欢
    • 1970-01-01
    • 2011-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-22
    • 2019-04-11
    • 1970-01-01
    相关资源
    最近更新 更多