Why am I getting a local reference table overflow? - android-ndk

I'm trying to figure out how to resolve a crash happening on my code when the number of cue points is high. My test reproduce it every time with a number of 530 cue items. Each cue has a dataSize of 1.
You can see my code below. Essentially native_update_cue_points is called from the java layer. This triggers multiple calls to the java layer from jni layer. Somehow this is causing a table overflow. I'm thinking somehow each call to the static java function is not releasing the allocated string values causing the stack overflow?
Sample code
static void native_update_cue_points(JNIEnv *env, jclass clazz)
{
if (smpMediaPlayer)
{
std::list<Cue> cuePoints;
smpMediaPlayer->getCuePoints(&cuePoints);
for (std::list<Cue>::iterator it = cuePoints.begin(); it != cuePoints.end(); it++)
{
Cue cue = *it;
java_update_cue_point(env, clazz, smFields.global_ref_thiz, 0, &cue);
}
}
}
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
if (!pCue) {
return;
}
jlong id = pCue->id;
jint type = pCue->type;
jint extra = pCue->extra;
jlong pos = pCue->pos;
jlong duration = pCue->duration;
jobjectArray jkeys = NULL;
jobjectArray jvalues = NULL;
int dataSize = pCue->data.size();
if (0 < dataSize)
{
jkeys = env->NewObjectArray(dataSize, env->FindClass("java/lang/String"), NULL);
jvalues = env->NewObjectArray(dataSize, env->FindClass("[B"), NULL);
int position = 0;
for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
{
// set key
env->SetObjectArrayElement(jkeys, position, env->NewStringUTF(it->first.c_str()));
// set value
jbyteArray byteArray = env->NewByteArray(it->second.length());
env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
env->SetObjectArrayElement(jvalues, position, byteArray);
env->DeleteLocalRef(byteArray);
position++;
}
}
// report update
env->CallStaticVoidMethod(
clazz,
smFields.native_callback_add_cue_point,
thiz,
(jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);
}
Stacktrace
JNI ERROR (app bug): local reference table overflow (max=512)
local reference table dump:
Last 10 entries (of 512):
511: 0x13054090 java.lang.String[] (1 elements)
510: 0x70732980 java.lang.Class<java.lang.String>
509: 0x130517c0 java.lang.String "song_artist"
508: 0x13054080 byte[][] (1 elements)
507: 0x7079af18 java.lang.Class<byte[]>
506: 0x13054070 java.lang.String[] (1 elements)
505: 0x70732980 java.lang.Class<java.lang.String>
504: 0x13051700 java.lang.String "song_artist"
503: 0x12d17ff0 byte[][] (1 elements)
502: 0x7079af18 java.lang.Class<byte[]>
Summary:
205 of java.lang.Class (2 unique instances)
103 of java.lang.String[] (1 elements) (103 unique instances)
102 of java.lang.String (102 unique instances)
102 of byte[][] (1 elements) (102 unique instances)
UPDATE 1:
I did a bit more digging and was able to narrow it down to the call FindClass. This leaves me even more confused! :/ Here is my simplified native_update_cue_point method. Do I need to release somehow the FindClass call!?
Code
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
jclass jstringClass = env->FindClass("java/lang/String");
}
Stacktrace
JNI ERROR (app bug): local reference table overflow (max=512)
local reference table dump:
Last 10 entries (of 512):
511: 0x70732980 java.lang.Class<java.lang.String>
510: 0x70732980 java.lang.Class<java.lang.String>
509: 0x70732980 java.lang.Class<java.lang.String>
508: 0x70732980 java.lang.Class<java.lang.String>
507: 0x70732980 java.lang.Class<java.lang.String>
506: 0x70732980 java.lang.Class<java.lang.String>
505: 0x70732980 java.lang.Class<java.lang.String>
504: 0x70732980 java.lang.Class<java.lang.String>
503: 0x70732980 java.lang.Class<java.lang.String>
502: 0x70732980 java.lang.Class<java.lang.String>
Summary:
512 of java.lang.Class (1 unique instances)
UPDATE 2:
I was able to fix the table overflow by doing a DeleteLocalRef on every local object regardless if the java VM would take care of it. My thoughts are that the VM is accumulating items on the stack which will release after executing. I need to remove each item explicitly from the stack to avoid unwanted accumulations for later removal while on a large loop.
Still I'm left feeling like I haven't understood completely why this is happening and why my solution solves it.
Fixed code
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
if (!pCue) {
return;
}
jclass jstringClass = env->FindClass("java/lang/String");
jclass jbyteArrayClass = env->FindClass("[B");
jlong id = pCue->id;
jint type = pCue->type;
jint extra = pCue->extra;
jlong pos = pCue->pos;
jlong duration = pCue->duration;
jobjectArray jkeys = NULL;
jobjectArray jvalues = NULL;
int dataSize = pCue->data.size();
if (0 < dataSize)
{
jkeys = env->NewObjectArray(dataSize, jstringClass, NULL);
jvalues = env->NewObjectArray(dataSize, jbyteArrayClass, NULL);
int position = 0;
for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
{
// set key
jstring jkey = env->NewStringUTF(it->first.c_str());
env->SetObjectArrayElement(jkeys, position, jkey);
env->DeleteLocalRef(jkey); // FIX
// set value
jbyteArray byteArray = env->NewByteArray(it->second.length());
env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
env->SetObjectArrayElement(jvalues, position, byteArray);
env->DeleteLocalRef(byteArray);
position++;
}
}
// report update
env->CallStaticVoidMethod(
clazz,
smFields.native_callback_add_cue_point,
thiz,
(jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);
if (jkeys) {
env->DeleteLocalRef(jkeys); // FIX
}
if (jvalues) {
env->DeleteLocalRef(jvalues); // FIX
}
if (jstringClass) {
env->DeleteLocalRef(jstringClass); // FIX
}
if (jbyteArrayClass) {
env->DeleteLocalRef(jbyteArrayClass); // FIX
}
}

You're creating too many local references (two for classes, two for object arrays and some more for strings and byte arrays per run of java_update_cue_point). The VM can only handle a limited amount of local references. See "Referencing Java Objects" for some documentation. The "Global and Local References" section in the JNI function documentation also has some details. The VM will free local references automatically when a native method returns, but in a loop like yours you will have to delete references you don't need anymore with DeleteLocalRef.
To optimize a little, my recommendation would be to do the two FindClass calls outside of the loop in native_update_cue_points and pass them as parameters to java_update_cue_point. This way you'll only have two references for the class objects that the VM will free for you when native_update_cue_points returns, and you save processing time by not finding the same classes over and over again.

I suspect you need to call DeleteLocalRef() on the Java String object created in this line of code:
env->SetObjectArrayElement(jkeys, position, env->NewStringUTF(it->first.c_str()));
Your call to NewStringUTF() creates a Java object with a local reference, which you do not delete.

Related

Passing a std::unique_ptr as a void* parameter to a function (CPtrArray::Add)

My code:
void CAssignSelectedColumnDlg::AddColumnData(CString strHeading, CStringArray* pAryStrNames, int iColumnIndex, int iCustomIndex /*-1*/, BOOL bFixedCustomType /*FALSE*/)
{
//COLUMN_DATA_S *psData = nullptr;
//psData = new COLUMN_DATA_S;
auto psData = std::make_unique<COLUMN_DATA_S>();
if (psData != nullptr)
{
// Note: we don't "own" pAryStrNames
psData->strHeading = strHeading;
psData->pAryStrNames = pAryStrNames;
psData->sActionListInfo.byColumnIndex = iColumnIndex;
psData->sActionListInfo.byCustomIndex = iCustomIndex;
psData->sActionListInfo.bFixedCustomType = bFixedCustomType ? true : false;
m_aryPtrColumnData.Add(psData);
}
}
Code analysis was suggesting I use std::make_unique instead of new. So I adjusted teh code but now get a compile error. I tried to find a suitable approach:
No suitable conversion function from std::unique_ptr<COLUMN_DATA_S>, std::default_delete<COLUMN_DATA_S>> to void * exists.
The function in question is CPtrArray::Add. I am still trying to gras the smart pointers and their usage in a context like this.
Note that we should not be deleting the pointer when the function ends. The deletion of the collection is done in a dialog event handler.

Differences between new String and Bytes.toString

I am using hbase utility org.apache.hadoop.hbase.util.Bytes
I generated a an array of Bytes from a string (in a example in Scala):
val bytes = Bytes.toBytes("test")
and want to convert back in String.
What is the difference between new String(bytes,"UTF-8") and Bytes.toString(bytes)
They both works.
At a guess that you are talking about https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/util/Bytes.html, basically nothing: Bytes.toString will call new String, except if the array is empty. You can see the method called here:
public static String toString(final byte [] b, int off, int len) {
if (b == null) {
return null;
}
if (len == 0) {
return "";
}
return new String(b, off, len, UTF8_CHARSET);
}
For the future, please mention any libraries you are using in the question (and the question is completely unrelated to Scala).

How to define Task with parameters and return value in c++\cli?

i have a class in a cs file:
public class ThreadData
{
private int index;
public ThreadData(int index)
{
this.index = index;
}
public static ThreadDataOutput DoWork(ThreadDataInput input)
{
return new ThreadDataOutput();
}
}
now, i have c++ code that tries to init a new task and to us the above function:
int numOfThread = 2;
array<Task^>^ taskArr = gcnew array<Task^>(numOfThread);
for (int i = 0; i < numOfThread; i++)
{
ThreadData^ td = gcnew ThreadData(i);
ThreadDataInput^ input = gcnew ThreadDataInput(i);
Task<ThreadDataOutput^>^ task = gcnew Task<ThreadDataOutput^>(td->DoWork, input);
taskArr[i] = task;
taskArr[i]->Start();
}
Task::WaitAll(taskArr, 300 * 1000);
the following code return 2 errors at compile time:
can't take address of 'ThreadData::DoWork' unless creating delegate instance
cannot convert argument 1 from 'AmadeusWS::ThreadDataOutput ^(__clrcall *)(AmadeusWS::ThreadDataInput ^)' to 'System::Func ^
i also tried to declare a delegate like this in the cs file:
public static Func<ThreadDataInput, ThreadDataOutput> DoWork2 = delegate(ThreadDataInput taskDataInput)
{
return new ThreadDataOutput();
};
but i don't know how to call it from the c++\cli code
can anyone assist me to understand how to define cli delegate that can take parametr ?
thanks
In order to create a delegate instance in C++/CLI, you need to construct it explicitly, and specify the object that it will be called on separately from the class & method to be called.
gcnew Func<TInput, TOutput>(theObject, &TheClass::MethodToInvoke)
Note that the method to be called is specified in the C++ style.
Substituting that in to your task creation, I believe this statement will work for you:
Task<ThreadDataOutput^>^ task = gcnew Task<ThreadDataOutput^>(
gcnew Func<ThreadDataInput^, ThreadDataOutput^>(td, &ThreadData::DoWork),
input);
Edit
In the code you posted in your comment, you missed the object to invoke the delegate on.
gcnew Func<Object^, Object^>(td, &ThreadData::DoWork)
^^

ConcurrentModificationException in HashSet

I have my code as below and I'm getting ConcurrentModificationException, particularly in the line for (String file : files)
I don't change anything for the "file" when doing iteration, so why the exception will be caused and how should I avoid it? Thanks for any suggestion!
int getTotalLength(final HashSet<String> files) {
int total = 0;
int len;
for (String file : files) {
len = getLength(file);
if (len != Long.MIN_VALUE) {
total += len;
}
}
return total;
}
int getLength(String file) {
int len = Long.MIN_VALUE;
if (file == null) {
return len;
}
File f = new File(file);
if (f.exists() && f.isFile()) {
len = f.length();
}
return size;
}
Refering to you comment, declaring final HashSet<String> files makes variable files finale - that means that you cannot assign another object to this variable inside this variable's scope. HashSet itself is mutable object and can be modified - it has nothing to do with final modifier (reference to the set object itselt is still the same).
If you want to work concurently on same object (same hashset) use synchronized blocks or methods.
Generally speaking, you cannot modify collection (in same or another thread) that are beeing iterated with for loop in for-each alike variant.

Calling java method contain string as argument and string as return type from c++ in ndk in android

I have one following methods in java:
public native String jniStringMethod();
public String stringMethod(String s) {
Log.d("Testing", "String:" + s);
return s;
}
I am trying to call "stringMethod" method in jniStringMethod() in cpp file in the following way:
jstring Java_ashok_learning_ndk_SampleNDKActivity_jniStringMethod(JNIEnv *env,
jobject obj) {
jstring jstr = env->NewStringUTF("This comes from jni string .");
//jclass clazz = env->GetObjectClass(obj);
jclass clazz = env->FindClass("ashok/learning/ndk/SampleNDKActivity");
if (0 == clazz) {
LOG("clazz class not found!");
}
jmethodID messageMe = env->GetMethodID(clazz, "stringMethod", "(Ljava/lang/String;)Ljava/lang/String;");
if (0 == messageMe) {
LOG("messageMe method not found!");
}
jobject result = env->CallObjectMethod(obj, messageMe, jstr);
LOG("result: %d", result);
const char* str = env->GetStringUTFChars((jstring)result, NULL); // should be released but what a heck, it's a tutorial :)
printf("%s\n", str);
return env->NewStringUTF(str);
}
But it is not getting called..and i am getting log as "messageMe method not found!",means method is not matching with signature...any one can suggest about my mistakes?
Your signture is OK. Are you sure your code is not executed properly even though messageMe is NULL? It has happend to me that my code was running fine despite jmethodID being NULL.

Resources