JNI Local Reference Changes in ICS

转载 2012年08月22日 10:54:11


[This post is by Elliott Hughes, a Software package Engineer on the Dalvik staff.&nbsp&mdash&nbspTim&nbspBray]

If you don’t publish native code that utilizes JNI, you can end reading through now. If you do publish native code that employs JNI, you truly need to have to read this.

What’s modifying, and why?

Each and every developer wants a great rubbish collector. The very best garbage collectors transfer objects about. This allows them offer really low-cost allocation and bulk deallocation, avoids heap fragmentation, and could increase locality. Heading objects about is a difficulty if you’ve handed out pointers to them to native code. JNI utilizes kinds this sort of as jobject to resolve this problem: rather than handing out direct pointers, you’re provided an opaque take care of that can be traded in for a pointer when essential. By making use of handles, when the rubbish collector moves an object, it just has to update the handle table to point to the object’s new place. This implies that native code won’t be left holding dangling pointers every time the garbage collector runs.

In previous releases of Android, we did not use indirect handles we utilised direct pointers. This did not seem to be like a dilemma as prolonged as we didn’t have a rubbish collector that moves objects, but it permit you compose buggy code that nevertheless seemed to perform. In Ice Cream Sandwich, even though we have not yet applied these a garbage collector, we have moved to indirect references so you can start detecting bugs in your native code.

Ice Cream Sandwich attributes a JNI bug compatibility mode so that as prolonged as your AndroidManifest.xml’s targetSdkVersion is less than Ice Cream Sandwich, your code is exempt. But as soon as you update your targetSdkVersion, your code demands to be appropriate.

CheckJNI has been updated to detect and report these problems, and in Ice Cream Sandwich, CheckJNI is on by default if debuggable="correct" in your manifest.

A quick primer on JNI references

In JNI, there are numerous sorts of reference. The two most important kinds are regional references and global references. Any provided jobject can be either local or world-wide. (There are weak globals as well, but they have a separate sort, jweak, and aren’t exciting here.)

The international/local distinction has an effect on each life time and scope. A worldwide is usable from any thread, using that thread’s JNIEnv*, and is legitimate till an explicit get in touch with toDeleteGlobalRef(). A local is only usable from the thread it was initially handed to, and is valid until finally possibly an explicit phone to DeleteLocalRef() or, much more commonly, until you return from your native technique. When a native technique returns, all neighborhood references are automatically deleted.

In the outdated technique, where neighborhood references have been direct pointers, nearby references had been by no means genuinely invalidated. That meant you could use a local reference indefinitely, even if you’d explicitly named DeleteLocalRef() on it, or implicitly deleted it withPopLocalFrame()!

Though any given JNIEnv* is only valid for use on 1 thread, simply because Android by no means had any per-thread state in a JNIEnv*, it used to be possible to get absent with making use of a JNIEnv* on the improper thread. Now there is a per-thread regional reference table, it’s important that you only use a JNIEnv* on the appropriate thread.

These are the bugs that ICS will detect. I’ll go through a couple of typical situations to illustrate these issues, how to spot them, and how to resolve them. It’s critical that you do fix them, since it’s likely that potential Android releases will employ relocating collectors. It will not be feasible to provide a bug-compatibility mode indefinitely.

Common JNI reference bugs

Bug: Forgetting to phone NewGlobalRef() when stashing a jobject in a native peer

If you have a native peer (a lengthy-lived native object corresponding to a Java object, normally created when the Java object is created and destroyed when the Java object’s finalizer runs), you should not stash a jobject in that native object, because it will not be valid subsequent time you attempt to use it. (Equivalent is true of JNIEnv*s. They may be valid if the following native phone occurs on the exact same thread, but they will not be valid in any other case.)

 class MyPeer
 MyPeer(jstring s)
 str_ = s // Error: stashing a reference with no guaranteeing it’s global.

 jstring str_

 static jlong MyClass_newPeer(JNIEnv* env, jclass)
 jstring regional_ref = env->NewStringUTF("hi there, entire world!")
 MyPeer* peer = new MyPeer(regional_ref)
 return static_cast&ltjlong>(reinterpret_cast&ltuintptr_t>(peer))
 // Error: local_ref is no more time valid when we return, but we've saved it in 'peer'.

 static void MyClass_printString(JNIEnv* env, jclass, jlong peerAddress)
 MyPeer* peer = reinterpret_cast&ltMyPeer*>(static_cast&ltuintptr_t>(peerAddress))
 // Error: peer->str_ is invalid!
 ScopedUtfChars s(env, peer->str_)
 std::cout &lt&lt s.do_str() &lt&lt std::endl

The correct for this is to only keep JNI worldwide references. Simply because there is in no way any automatic cleanup of JNI worldwide references, it’s critically important that you clean them up oneself. This is created somewhat awkward by the fact that your destructor will not have a JNIEnv*. The least complicated fix is usually to have an explicit ‘destroy‘ purpose for your native peer, named from the Java peer’s finalizer:

 course MyPeer
 MyPeer(JNIEnv* env, jstring s)
 this->s = env->NewGlobalRef(s)

 assert(s == NULL)

 void ruin(JNIEnv* env)
 s = NULL

 jstring s

You ought to often have matching calls to NewGlobalRef()/DeleteGlobalRef(). CheckJNI will catch global reference leaks, but the restrict is quite large (2000 by default), so observe out.

If you do have this class of error in your code, the crash will appear something like this:

 JNI ERROR (app bug): accessed stale nearby reference 0x5900021 (index eight in a table of size eight)
 JNI WARNING: jstring is an invalid neighborhood reference (0x5900021)
 in LMyClass.printString:(J)V (GetStringUTFChars)
 "principal" prio=5 tid=one RUNNABLE
 | group="major" sCount= dsCount= obj=0xf5e96410 self=0x8215888
 | sysTid=11044 great= sched=/ cgrp=[n/a] deal with=-152574256
 | schedstat=( 156038824 600810 47 ) utm=14 stm=2 core=
 at MyClass.printString(Native Approach)
 at MyClass.principal(MyClass.java:thirteen)

If you’re making use of yet another thread’s JNIEnv*, the crash will appear a thing like this:

 JNI WARNING: threadid=eight utilizing env from threadid=1
 in LMyClass.printString:(J)V (GetStringUTFChars)
 "Thread-ten" prio=five tid=eight NATIVE
 | group="major" sCount= dsCount= obj=0xf5f77d60 self=0x9f8f248
 | sysTid=22299 nice= sched=/ cgrp=[n/a] deal with=-256476304
 | schedstat=( 153358572 709218 48 ) utm=12 stm=four core=eight
 at MyClass.printString(Native Technique)
 at MyClass$  1.operate(MyClass.java:15)

Bug: Mistakenly assuming FindClass() returns global references

FindClass() returns local references. A lot of folks believe otherwise. In a technique with no course unloading (like Android), you can treat jfieldID and jmethodID as if they had been global. (They are not actually references, but in a system with course unloading there are equivalent life time concerns.) But jclass is a reference, and FindClass() returns neighborhood references. A typical bug pattern is “static jclass”. Unless of course you’re manually turning your nearby references into international references, your code is broken. Here’s what appropriate code must seem like:

 static jclass gMyClass
 static jclass gSomeClass

 static void MyClass_nativeInit(JNIEnv* env, jclass myClass)
 // ‘myClass’ (and any other non-primitive arguments) are only local references.
 gMyClass = env->NewGlobalRef(myClass)

 // FindClass only returns nearby references.
 jclass someClass = env->FindClass("SomeClass")
 if (someClass == NULL)
 return // FindClass already threw an exception this kind of as NoClassDefFoundError.

 gSomeClass = env->NewGlobalRef(someClass)

If you do have this class of error in your code, the crash will look a thing like this:

 JNI ERROR (app bug): try to use stale nearby reference 0x4200001d (must be 0x4210001d)
 JNI WARNING: 0x4200001d is not a legitimate JNI reference
 in LMyClass.useStashedClass:()V (IsSameObject)

Bug: Calling DeleteLocalRef() and continuing to use the deleted reference

It shouldn’t require to be explained that it is illegal to proceed to use a reference right after callingDeleteLocalRef() on it, but since it utilized to operate, so you may possibly have produced this error and not recognized. The typical pattern appears to be wherever native code has a long-working loop, and builders consider to clean up each and every simple local reference as they go to steer clear of hitting the neighborhood reference limit, but they unintentionally also delete the reference they want to use as a return price!

The resolve is trivial: really do not phone DeleteLocalRef() on a reference you’re heading to use (wherever “use” incorporates “return”).

Bug: Calling PopLocalFrame() and continuing to use a popped reference

This is a more delicate variant of the preceding bug. The PushLocalFrame() and PopLocalFrame()calls permit you bulk-delete regional references. When you get in touch with PopLocalFrame(), you pass in the 1 reference from the body that you’d like to retain (normally for use as a return worth), or NULL. In the prior, you’d get away with incorrect code like the following:

 static jobjectArray MyClass_returnArray(JNIEnv* env, jclass)
 jobjectArray array = env->NewObjectArray(128, gMyClass, NULL)
 for (int i = i &lt 128 ++i)
 env->SetObjectArrayElement(array, i, newMyClass(i))

 env->PopLocalFrame(NULL) // Error: should pass 'array'.
 return array // Error: array is no extended legitimate.

The correct is generally to pass the reference to PopLocalFrame(). Notice in the over example that you do not need to preserve references to the personal array elements as extended as the GC understands about the array alone, it’ll just take treatment of the aspects (and any objects they point to in flip) itself.

If you do have this course of error in your code, the crash will appear some thing like this:

 JNI ERROR (app bug): accessed stale nearby reference 0x2d00025 (index 9 in a table of size   JNI WARNING: invalid reference returned from native code
 in LMyClass.returnArray:()[Ljava/lang/Object

Wrapping up

Sure, we asking for a little bit much more interest to detail in your JNI coding, which is additional function. But we feel that you will come out forward on the deal as we roll in much better and far more refined memory administration code.

您举报文章:JNI Local Reference Changes in ICS