Graphics.cpp revision 1b22b979256cf163ab9bbfd4fcfa16a8ce862ed1
1#include "jni.h"
2#include "GraphicsJNI.h"
3#include "NIOBuffer.h"
4#include "SkPicture.h"
5#include "SkRegion.h"
6#include <android_runtime/AndroidRuntime.h>
7
8//#define REPORT_SIZE_TO_JVM
9//#define TRACK_LOCK_COUNT
10
11void doThrow(JNIEnv* env, const char* exc, const char* msg) {
12    // don't throw a new exception if we already have one pending
13    if (env->ExceptionCheck() == JNI_FALSE) {
14        jclass npeClazz;
15
16        npeClazz = env->FindClass(exc);
17        LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
18
19        env->ThrowNew(npeClazz, msg);
20    }
21}
22
23void doThrowNPE(JNIEnv* env) {
24    doThrow(env, "java/lang/NullPointerException");
25}
26
27void doThrowAIOOBE(JNIEnv* env) {
28    doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
29}
30
31void doThrowRE(JNIEnv* env, const char* msg) {
32    doThrow(env, "java/lang/RuntimeException", msg);
33}
34
35void doThrowIAE(JNIEnv* env, const char* msg) {
36    doThrow(env, "java/lang/IllegalArgumentException", msg);
37}
38
39void doThrowISE(JNIEnv* env, const char* msg) {
40    doThrow(env, "java/lang/IllegalStateException", msg);
41}
42
43void doThrowOOME(JNIEnv* env, const char* msg) {
44    doThrow(env, "java/lang/OutOfMemoryError", msg);
45}
46
47bool GraphicsJNI::hasException(JNIEnv *env) {
48    if (env->ExceptionCheck() != 0) {
49        LOGE("*** Uncaught exception returned from Java call!\n");
50        env->ExceptionDescribe();
51        return true;
52    }
53    return false;
54}
55
56///////////////////////////////////////////////////////////////////////////////
57
58AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
59                                       int minLength)
60: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
61    SkASSERT(env);
62    if (array) {
63        fLen = env->GetArrayLength(array);
64        if (fLen < minLength) {
65            sk_throw();
66        }
67        fPtr = env->GetFloatArrayElements(array, NULL);
68    }
69}
70
71AutoJavaFloatArray::~AutoJavaFloatArray() {
72    if (fPtr) {
73        fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
74    }
75}
76
77AutoJavaIntArray::AutoJavaIntArray(JNIEnv* env, jintArray array,
78                                       int minLength)
79: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
80    SkASSERT(env);
81    if (array) {
82        fLen = env->GetArrayLength(array);
83        if (fLen < minLength) {
84            sk_throw();
85        }
86        fPtr = env->GetIntArrayElements(array, NULL);
87    }
88}
89
90AutoJavaIntArray::~AutoJavaIntArray() {
91    if (fPtr) {
92        fEnv->ReleaseIntArrayElements(fArray, fPtr, 0);
93    }
94}
95
96AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
97                                       int minLength)
98: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
99    SkASSERT(env);
100    if (array) {
101        fLen = env->GetArrayLength(array);
102        if (fLen < minLength) {
103            sk_throw();
104        }
105        fPtr = env->GetShortArrayElements(array, NULL);
106    }
107}
108
109AutoJavaShortArray::~AutoJavaShortArray() {
110    if (fPtr) {
111        fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
112    }
113}
114
115AutoJavaByteArray::AutoJavaByteArray(JNIEnv* env, jbyteArray array,
116                                       int minLength)
117: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
118    SkASSERT(env);
119    if (array) {
120        fLen = env->GetArrayLength(array);
121        if (fLen < minLength) {
122            sk_throw();
123        }
124        fPtr = env->GetByteArrayElements(array, NULL);
125    }
126}
127
128AutoJavaByteArray::~AutoJavaByteArray() {
129    if (fPtr) {
130        fEnv->ReleaseByteArrayElements(fArray, fPtr, 0);
131    }
132}
133
134///////////////////////////////////////////////////////////////////////////////
135
136static jclass   gRect_class;
137static jfieldID gRect_leftFieldID;
138static jfieldID gRect_topFieldID;
139static jfieldID gRect_rightFieldID;
140static jfieldID gRect_bottomFieldID;
141
142static jclass   gRectF_class;
143static jfieldID gRectF_leftFieldID;
144static jfieldID gRectF_topFieldID;
145static jfieldID gRectF_rightFieldID;
146static jfieldID gRectF_bottomFieldID;
147
148static jclass   gPoint_class;
149static jfieldID gPoint_xFieldID;
150static jfieldID gPoint_yFieldID;
151
152static jclass   gPointF_class;
153static jfieldID gPointF_xFieldID;
154static jfieldID gPointF_yFieldID;
155
156static jclass   gBitmap_class;
157static jfieldID gBitmap_nativeInstanceID;
158static jmethodID gBitmap_constructorMethodID;
159static jmethodID gBitmap_allocBufferMethodID;
160
161static jclass   gBitmapConfig_class;
162static jfieldID gBitmapConfig_nativeInstanceID;
163
164static jclass   gCanvas_class;
165static jfieldID gCanvas_nativeInstanceID;
166static jfieldID gCanvas_densityScaleID;
167
168static jclass   gPaint_class;
169static jfieldID gPaint_nativeInstanceID;
170
171static jclass   gPicture_class;
172static jfieldID gPicture_nativeInstanceID;
173
174static jclass   gRegion_class;
175static jfieldID gRegion_nativeInstanceID;
176static jmethodID gRegion_constructorMethodID;
177
178static jobject   gVMRuntime_singleton;
179static jmethodID gVMRuntime_trackExternalAllocationMethodID;
180static jmethodID gVMRuntime_trackExternalFreeMethodID;
181
182///////////////////////////////////////////////////////////////////////////////
183
184void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
185{
186    SkASSERT(env->IsInstanceOf(obj, gRect_class));
187
188    *L = env->GetIntField(obj, gRect_leftFieldID);
189    *T = env->GetIntField(obj, gRect_topFieldID);
190    *R = env->GetIntField(obj, gRect_rightFieldID);
191    *B = env->GetIntField(obj, gRect_bottomFieldID);
192}
193
194void GraphicsJNI::set_jrect(JNIEnv* env, jobject obj, int L, int T, int R, int B)
195{
196    SkASSERT(env->IsInstanceOf(obj, gRect_class));
197
198    env->SetIntField(obj, gRect_leftFieldID, L);
199    env->SetIntField(obj, gRect_topFieldID, T);
200    env->SetIntField(obj, gRect_rightFieldID, R);
201    env->SetIntField(obj, gRect_bottomFieldID, B);
202}
203
204SkIRect* GraphicsJNI::jrect_to_irect(JNIEnv* env, jobject obj, SkIRect* ir)
205{
206    SkASSERT(env->IsInstanceOf(obj, gRect_class));
207
208    ir->set(env->GetIntField(obj, gRect_leftFieldID),
209            env->GetIntField(obj, gRect_topFieldID),
210            env->GetIntField(obj, gRect_rightFieldID),
211            env->GetIntField(obj, gRect_bottomFieldID));
212    return ir;
213}
214
215void GraphicsJNI::irect_to_jrect(const SkIRect& ir, JNIEnv* env, jobject obj)
216{
217    SkASSERT(env->IsInstanceOf(obj, gRect_class));
218
219    env->SetIntField(obj, gRect_leftFieldID, ir.fLeft);
220    env->SetIntField(obj, gRect_topFieldID, ir.fTop);
221    env->SetIntField(obj, gRect_rightFieldID, ir.fRight);
222    env->SetIntField(obj, gRect_bottomFieldID, ir.fBottom);
223}
224
225SkRect* GraphicsJNI::jrectf_to_rect(JNIEnv* env, jobject obj, SkRect* r)
226{
227    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
228
229    r->set(SkFloatToScalar(env->GetFloatField(obj, gRectF_leftFieldID)),
230           SkFloatToScalar(env->GetFloatField(obj, gRectF_topFieldID)),
231           SkFloatToScalar(env->GetFloatField(obj, gRectF_rightFieldID)),
232           SkFloatToScalar(env->GetFloatField(obj, gRectF_bottomFieldID)));
233    return r;
234}
235
236SkRect* GraphicsJNI::jrect_to_rect(JNIEnv* env, jobject obj, SkRect* r)
237{
238    SkASSERT(env->IsInstanceOf(obj, gRect_class));
239
240    r->set(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)),
241           SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)),
242           SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)),
243           SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID)));
244    return r;
245}
246
247void GraphicsJNI::rect_to_jrectf(const SkRect& r, JNIEnv* env, jobject obj)
248{
249    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
250
251    env->SetFloatField(obj, gRectF_leftFieldID, SkScalarToFloat(r.fLeft));
252    env->SetFloatField(obj, gRectF_topFieldID, SkScalarToFloat(r.fTop));
253    env->SetFloatField(obj, gRectF_rightFieldID, SkScalarToFloat(r.fRight));
254    env->SetFloatField(obj, gRectF_bottomFieldID, SkScalarToFloat(r.fBottom));
255}
256
257SkIPoint* GraphicsJNI::jpoint_to_ipoint(JNIEnv* env, jobject obj, SkIPoint* point)
258{
259    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
260
261    point->set(env->GetIntField(obj, gPoint_xFieldID),
262               env->GetIntField(obj, gPoint_yFieldID));
263    return point;
264}
265
266void GraphicsJNI::ipoint_to_jpoint(const SkIPoint& ir, JNIEnv* env, jobject obj)
267{
268    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
269
270    env->SetIntField(obj, gPoint_xFieldID, ir.fX);
271    env->SetIntField(obj, gPoint_yFieldID, ir.fY);
272}
273
274SkPoint* GraphicsJNI::jpointf_to_point(JNIEnv* env, jobject obj, SkPoint* point)
275{
276    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
277
278    point->set(SkFloatToScalar(env->GetIntField(obj, gPointF_xFieldID)),
279               SkFloatToScalar(env->GetIntField(obj, gPointF_yFieldID)));
280    return point;
281}
282
283void GraphicsJNI::point_to_jpointf(const SkPoint& r, JNIEnv* env, jobject obj)
284{
285    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
286
287    env->SetFloatField(obj, gPointF_xFieldID, SkScalarToFloat(r.fX));
288    env->SetFloatField(obj, gPointF_yFieldID, SkScalarToFloat(r.fY));
289}
290
291SkBitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
292    SkASSERT(env);
293    SkASSERT(bitmap);
294    SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
295    SkBitmap* b = (SkBitmap*)env->GetIntField(bitmap, gBitmap_nativeInstanceID);
296    SkASSERT(b);
297    return b;
298}
299
300SkBitmap::Config GraphicsJNI::getNativeBitmapConfig(JNIEnv* env,
301                                                    jobject jconfig) {
302    SkASSERT(env);
303    if (NULL == jconfig) {
304        return SkBitmap::kNo_Config;
305    }
306    SkASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class));
307    int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
308    if (c < 0 || c >= SkBitmap::kConfigCount) {
309        c = SkBitmap::kNo_Config;
310    }
311    return static_cast<SkBitmap::Config>(c);
312}
313
314SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
315    SkASSERT(env);
316    SkASSERT(canvas);
317    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
318    SkCanvas* c = (SkCanvas*)env->GetIntField(canvas, gCanvas_nativeInstanceID);
319    SkASSERT(c);
320    return c;
321}
322
323SkScalar GraphicsJNI::getCanvasDensityScale(JNIEnv* env, jobject canvas) {
324    SkASSERT(env);
325    SkASSERT(canvas);
326    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
327    return SkFloatToScalar(env->GetFloatField(canvas, gCanvas_densityScaleID));
328}
329
330SkPaint* GraphicsJNI::getNativePaint(JNIEnv* env, jobject paint) {
331    SkASSERT(env);
332    SkASSERT(paint);
333    SkASSERT(env->IsInstanceOf(paint, gPaint_class));
334    SkPaint* p = (SkPaint*)env->GetIntField(paint, gPaint_nativeInstanceID);
335    SkASSERT(p);
336    return p;
337}
338
339SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
340{
341    SkASSERT(env);
342    SkASSERT(picture);
343    SkASSERT(env->IsInstanceOf(picture, gPicture_class));
344    SkPicture* p = (SkPicture*)env->GetIntField(picture, gPicture_nativeInstanceID);
345    SkASSERT(p);
346    return p;
347}
348
349SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
350{
351    SkASSERT(env);
352    SkASSERT(region);
353    SkASSERT(env->IsInstanceOf(region, gRegion_class));
354    SkRegion* r = (SkRegion*)env->GetIntField(region, gRegion_nativeInstanceID);
355    SkASSERT(r);
356    return r;
357}
358
359///////////////////////////////////////////////////////////////////////////////////////////
360
361jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
362                                  jbyteArray ninepatch)
363{
364    SkASSERT(bitmap != NULL);
365    SkASSERT(NULL != bitmap->pixelRef());
366
367    jobject obj = env->AllocObject(gBitmap_class);
368    if (obj) {
369        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
370                            (jint)bitmap, isMutable, ninepatch);
371        if (hasException(env)) {
372            obj = NULL;
373        }
374    }
375    return obj;
376}
377
378jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
379{
380    SkASSERT(region != NULL);
381    jobject obj = env->AllocObject(gRegion_class);
382    if (obj) {
383        env->CallVoidMethod(obj, gRegion_constructorMethodID, (jint)region, 0);
384        if (hasException(env)) {
385            obj = NULL;
386        }
387    }
388    return obj;
389}
390
391#include "SkPixelRef.h"
392
393static JNIEnv* vm2env(JavaVM* vm)
394{
395    JNIEnv* env = NULL;
396    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK || NULL == env)
397    {
398        SkDebugf("------- [%p] vm->GetEnv() failed\n", vm);
399        sk_throw();
400    }
401    return env;
402}
403
404#ifdef TRACK_LOCK_COUNT
405    static int gLockCount;
406#endif
407
408///////////////////////////////////////////////////////////////////////////////
409
410#include "SkMallocPixelRef.h"
411
412/*  Extend SkMallocPixelRef to inform the VM when we free up the storage
413*/
414class AndroidPixelRef : public SkMallocPixelRef {
415public:
416    /** Allocate the specified buffer for pixels. The memory is freed when the
417        last owner of this pixelref is gone. Our caller has already informed
418        the VM of our allocation.
419    */
420    AndroidPixelRef(JNIEnv* env, void* storage, size_t size,
421            SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
422        SkASSERT(storage);
423        SkASSERT(env);
424
425        if (env->GetJavaVM(&fVM) != JNI_OK) {
426            SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
427            sk_throw();
428        }
429    }
430
431    virtual ~AndroidPixelRef() {
432        JNIEnv* env = vm2env(fVM);
433//        SkDebugf("-------------- inform VM we're releasing %d bytes\n", this->getSize());
434        jlong jsize = this->getSize();  // the VM wants longs for the size
435        env->CallVoidMethod(gVMRuntime_singleton,
436                            gVMRuntime_trackExternalFreeMethodID,
437                            jsize);
438        if (GraphicsJNI::hasException(env)) {
439            env->ExceptionClear();
440        }
441    }
442
443private:
444    JavaVM* fVM;
445};
446
447bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
448                                  SkColorTable* ctable, bool reportSizeToVM) {
449    Sk64 size64 = bitmap->getSize64();
450    if (size64.isNeg() || !size64.is32()) {
451        doThrow(env, "java/lang/IllegalArgumentException",
452                     "bitmap size exceeds 32bits");
453        return false;
454    }
455
456    size_t size = size64.get32();
457    jlong jsize = size;  // the VM wants longs for the size
458    if (reportSizeToVM) {
459        //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
460        bool r = env->CallBooleanMethod(gVMRuntime_singleton,
461                                    gVMRuntime_trackExternalAllocationMethodID,
462                                    jsize);
463        if (GraphicsJNI::hasException(env)) {
464            return false;
465        }
466        if (!r) {
467            LOGE("VM won't let us allocate %zd bytes\n", size);
468            doThrowOOME(env, "bitmap size exceeds VM budget");
469            return false;
470        }
471    }
472    // call the version of malloc that returns null on failure
473    void* addr = sk_malloc_flags(size, 0);
474    if (NULL == addr) {
475        if (reportSizeToVM) {
476            //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
477            // we didn't actually allocate it, so inform the VM
478            env->CallVoidMethod(gVMRuntime_singleton,
479                                 gVMRuntime_trackExternalFreeMethodID,
480                                 jsize);
481            if (!GraphicsJNI::hasException(env)) {
482                doThrowOOME(env, "bitmap size too large for malloc");
483            }
484        }
485        return false;
486    }
487
488    SkPixelRef* pr = reportSizeToVM ?
489                        new AndroidPixelRef(env, addr, size, ctable) :
490                        new SkMallocPixelRef(addr, size, ctable);
491    bitmap->setPixelRef(pr)->unref();
492    // since we're already allocated, we lockPixels right away
493    // HeapAllocator behaves this way too
494    bitmap->lockPixels();
495    return true;
496}
497
498///////////////////////////////////////////////////////////////////////////////
499
500JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM)
501    : fEnv(env), fReportSizeToVM(reportSizeToVM) {}
502
503bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
504    return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable, fReportSizeToVM);
505}
506
507////////////////////////////////////////////////////////////////////////////////
508
509static jclass make_globalref(JNIEnv* env, const char classname[])
510{
511    jclass c = env->FindClass(classname);
512    SkASSERT(c);
513    return (jclass)env->NewGlobalRef(c);
514}
515
516static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
517                                const char fieldname[], const char type[])
518{
519    jfieldID id = env->GetFieldID(clazz, fieldname, type);
520    SkASSERT(id);
521    return id;
522}
523
524int register_android_graphics_Graphics(JNIEnv* env)
525{
526    jmethodID m;
527    jclass c;
528
529    gRect_class = make_globalref(env, "android/graphics/Rect");
530    gRect_leftFieldID = getFieldIDCheck(env, gRect_class, "left", "I");
531    gRect_topFieldID = getFieldIDCheck(env, gRect_class, "top", "I");
532    gRect_rightFieldID = getFieldIDCheck(env, gRect_class, "right", "I");
533    gRect_bottomFieldID = getFieldIDCheck(env, gRect_class, "bottom", "I");
534
535    gRectF_class = make_globalref(env, "android/graphics/RectF");
536    gRectF_leftFieldID = getFieldIDCheck(env, gRectF_class, "left", "F");
537    gRectF_topFieldID = getFieldIDCheck(env, gRectF_class, "top", "F");
538    gRectF_rightFieldID = getFieldIDCheck(env, gRectF_class, "right", "F");
539    gRectF_bottomFieldID = getFieldIDCheck(env, gRectF_class, "bottom", "F");
540
541    gPoint_class = make_globalref(env, "android/graphics/Point");
542    gPoint_xFieldID = getFieldIDCheck(env, gPoint_class, "x", "I");
543    gPoint_yFieldID = getFieldIDCheck(env, gPoint_class, "y", "I");
544
545    gPointF_class = make_globalref(env, "android/graphics/PointF");
546    gPointF_xFieldID = getFieldIDCheck(env, gPointF_class, "x", "F");
547    gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F");
548
549    gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
550    gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
551    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
552                                            "(IZ[B)V");
553
554    gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config");
555    gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class,
556                                                     "nativeInt", "I");
557
558    gCanvas_class = make_globalref(env, "android/graphics/Canvas");
559    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "I");
560    gCanvas_densityScaleID = getFieldIDCheck(env, gCanvas_class, "mDensityScale", "F");
561
562    gPaint_class = make_globalref(env, "android/graphics/Paint");
563    gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "I");
564
565    gPicture_class = make_globalref(env, "android/graphics/Picture");
566    gPicture_nativeInstanceID = getFieldIDCheck(env, gPicture_class, "mNativePicture", "I");
567
568    gRegion_class = make_globalref(env, "android/graphics/Region");
569    gRegion_nativeInstanceID = getFieldIDCheck(env, gRegion_class, "mNativeRegion", "I");
570    gRegion_constructorMethodID = env->GetMethodID(gRegion_class, "<init>",
571        "(II)V");
572
573    // Get the VMRuntime class.
574    c = env->FindClass("dalvik/system/VMRuntime");
575    SkASSERT(c);
576    // Look up VMRuntime.getRuntime().
577    m = env->GetStaticMethodID(c, "getRuntime", "()Ldalvik/system/VMRuntime;");
578    SkASSERT(m);
579    // Call VMRuntime.getRuntime() and hold onto its result.
580    gVMRuntime_singleton = env->CallStaticObjectMethod(c, m);
581    SkASSERT(gVMRuntime_singleton);
582    gVMRuntime_singleton = (jobject)env->NewGlobalRef(gVMRuntime_singleton);
583    // Look up the VMRuntime methods we'll be using.
584    gVMRuntime_trackExternalAllocationMethodID =
585                        env->GetMethodID(c, "trackExternalAllocation", "(J)Z");
586    gVMRuntime_trackExternalFreeMethodID =
587                            env->GetMethodID(c, "trackExternalFree", "(J)V");
588
589    NIOBuffer::RegisterJNI(env);
590
591    return 0;
592}
593
594