简体   繁体   中英

How is memory freed when native libraries are called from Java and Python?

I'm using the Java wrappers for OpenCV. I've also used the Python wrappers before. I read the following about JNI:

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

I was curious about how memory dynamically allocated by native libraries is freed, both while using the Java wrappers and Python wrappers?

The JNI framework my not provide automatic garbage collection for external resources, but the wrappers providing bindings for a language will.

Python provides automatic garbage collection, but if you use the C API you have to manually acquire and release any references to python objects and manage references to native data. Py_INCREF() will increase the reference count of a PyObject * by one so that you can ensure it will be alive until you call Py_DECREF() on the same object later. The memory will then be freed once the reference count reaches zero (yes, this is a simplification because of the garbage collector and circular references, but it is true in principle).

Java (the JVM) also provides automatic garbage collection, but if you use the JNI you have to manually manage all references to your objects. This behaves similarly to Python by using (*env)->DeleteLocalRef() or (*env)->Release...() (depending on the type of object) to release the local references to objects acquired through the JNI.

Lets say you want to wrap the native C library 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);
}

So if you were to write Python bindings to the native foo library, you would need a wrapper Python object which stores a pointer to the native data which the wrapper would manage. And when the Python wrapper object is destroyed, it would have to have to free any pointers to native data and release any references to Python objects.

// 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.
};

To wrap the native foo library in Java, you would define your wrapper Java class. It would use JNI to communicate with the native library and store relevant native pointers (maybe a generic wrapper which creates one instance per native pointer). In the class destructor (or the closest you can get in Java), it would then use the native library to release the native library pointers.

// 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;
}

NOTE: These source examples are only meant as simple examples, have not been tested, may omit details, and may not necessarily follow established, best practices.

Python is configured in a way to collect the de-referenced Python objects. This is done by checking the reference count as Py_INCREF() for the PyObject , this also maps to the JVM Space where it calculated the Java Object Space. This process decrements until the count reaches to zero for the variables un-referenced.

We can say that the garbage has been removed once this connter reaches Zero. This wrapper object stores this complete data into a HashMap. Once the memory is freed the HashMap is also left for garbage collection. These techniques are used by JNI also for JVM which makes it compatible and flexible to use in this space.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM