Issue
I have a class containing a member variable of type long and a set of native method declarations. I believe the memory for the variable is being allocated in one of the native methods and the de-allocation is attempted in the finalize() method by calling another native method destroy. I understand that finalize is deprecated but prior to finding an alternative for finalize, I need to understand how the memory allocation is happening and how Java and C maintain sync through JNI and how this particular member variable is tracked. I will attempt to explain the scenario better with code snippets:
JSQ.java
class JSQ {
protected long shdl;
protected JSQ() { log("JSQ constructor"); }
protected JSQ(String stmt, boolean isd)
throws DhSQLException
{
// calls a set of native methods
set_shdl();
setstmt(stmt, shdl);
setd(isd, shdl);
prep(shdl);
}
public void finalize()
{
destroy(shdl);
}
private native void set_shdl() throws DhSQLException;
private native void setstmt(String s, long shdl) throws DhSQLException;
private native void setd(boolean b, long shdl);
private native void prep(long shdl) throws DhSQLException;
private native void destroy(long shdl);
protected void execute() throws DhSQLException
{
parExec(shdl);
}
protected native void parExec(long shdl);
}
JSQL.cxx
#define SQ ((sq_stsm_t *)(shdl))
JNIEXPORT void JNICALL Java_com_project_package_JSQ_set_shdl
(JNIEnv *env, jobject obj_This)
{
jclass cls;
jmethodID mid;
cls = (env)->GetObjectClass (obj_This);
mid = (env)->GetMethodID (hThis,"set_JSQ_shdl","(J)V");
status_t status;
// memory allocation
sq_stsm_t * S = new sq_stsm_t(status);
if(status)
{
if (S) { delete S; }
return;
}
// I understand that we're attempting to call a Java method from a native method.
// But which method is it calling?
// Also, are we converting S from sq_stms_t type to jlong?
(env)->CallVoidMethod (obj_This,mid,(jlong) S);
return;
}
JNIEXPORT void JNICALL Java_com_project_package_JSQ_setstmt
(JNIEnv *env, jobject, jstring jstmt, jlong shdl)
{
status_t status;
// cstmt is obtained using jstmt
// Note: #define SQ ((sq_stsm_t *)(shdl))
status = SQ->SetStmt(cstmt);
return;
}
JNIEXPORT void JNICALL Java_com_project_package_JSQ_destroy
(JNIEnv *, jobject, jlong shdl)
{
delete SQ;
}
The points where I'm confused:
Why long and jlong and the conversion from the user defined type sq_stsm_t to long. I understand that Java is not aware of this user defined type. But why the choice of long?
Why the #define on SQ and how exactly does destroy delete the memory allocated in set_shdl by calling delete on SQ?
Post this, I have to find a replacement for finalize - for which I have concluded that AutoCloseable with try-with-resources is, by far, my best option. Since there are a lot of native calls involved, I'm confused as to how I would go about it. But, that is a separate question to be addressed and I don't want to go into that here. Just mentioning to set some background on the use case.
Solution
A Java long
variable is guaranteed to be 64 bits wide, so it is enough to contain an address, even on a 64-bit system.
The cast to long
is just there to make the C++ type system happy.
As you see, the SQ
macro is used to convert this address back to a proper pointer to an sq_stsm_t
object.
As for your second question: all the macro does is get rid of some typing work.
The Java part of your program will always pass the long
-typed shdl
variable, so the SQ
macro just provides easy access to the actual sq_stsm_t
object.
Finally, new
and delete
in C++ go together: new
allocates memory and calls the constructor, delete
calls the destructor and frees the memory again.
Note that "free" does not necessarily mean "give back to the OS", so your process's memory usage can remain the same after this delete
operation.
As for the comment in your code: Java_com_project_package_JSQ_set_shdl
tries to call the Java method boolean com.project.package.JSQ#set_JSQ_shdl(jlong)
, although it is not present in the code you pasted. I guess it is a simple setter for the shdl
field?
As for your follow-up plans: implementing AutoCloseable
is a good idea. You would need to create a native void close()
method in your JSQ object which calls delete
on the sq_stsm_t
object stored in the shdl
field and then replaces the shdl
field with (jlong)nullptr
.
Answered By - Botje
Answer Checked By - David Goodson (JavaFixing Volunteer)