-
Notifications
You must be signed in to change notification settings - Fork 720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JCL: ensure JNI cache is initialized only once #17800
JCL: ensure JNI cache is initialized only once #17800
Conversation
@keithc-ca - thanks for the review, all the suggestions have been addressed. regarding squashing of mixup commits - not sure what is customary in this project (squash right away or wait for the review to complete and squash it before landing, or the committer squashes the commits while landing) . pls suggest. |
My preference is that you squash the commits before this change is merged. You can do it as the change evolves or all at once at the end - either way works for me. |
2788cd7
to
ff571e6
Compare
@keithc-ca - done, addressed review comments and squashed commits into one, pls have a look. |
I'd like a second opinion from @gacholio. |
ff571e6
to
265cc75
Compare
I don't think any of these changes are actually necessary - the changes are in two static functions that are called only from a JNI native, so looping case is simply not going to happen. |
265cc75
to
91e93ba
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved for correctness, but I'm still not convinced the extra code provides any benefit.
import java.lang.management.*;
public class TX {
public static void main(String args[]) {
while(true) {
start();
collect();
sleep();
}
}
public static void start() {
Thread t[] = new Thread[32];
for(int i=0; i<32; i++) {
t[i] = new Thread() {
public void run() {
measure();
}
};
}
for(int i=0; i<32; i++) {
t[i].start();
}
for(int i=0; i<32; i++) {
try {
t[i].join();
} catch(Exception e) {}
}
}
public static void measure() {
ThreadMXBean tb = ManagementFactory.getThreadMXBean();
long[] ids = tb.getAllThreadIds();
ThreadInfo[] info = tb.getThreadInfo(ids, true, true);
}
public static void collect() {
System.gc();
}
public static void sleep() {
try {
Thread.sleep(1);
} catch(Exception e) {}
}
}
@gacholio - here is a test case that manifests the leak. You can take dumps and look at the number of jniLocalReferences or their pool counts. The actual test case is much larger, in which case the program makes use of a service loop in the JNI code (not in Java), and frequently invoke leaking functions or its caller. Given the fact that there is no exit into Java, the frames are never auto-cleared. |
Calling VM-provided natives from a C loop is not something we can support - they will change or be removed without notice. @tajila Comments? |
sorry, I should have been more explicit. The C loop is calling the public JNI function - in this case |
My comment stands - this is not a dependable way to use the VM. |
ok, fair point, let us focus only on the above test case then. |
Oh, the java test leaks as well? |
yes. reviewing my original use case, I think it is slightly different:
looks like the local references are not cleared when the JNI returns to immediate calling Java frame [2], instead it expects all the native frames (including [1]) to be unwound? I can share my test case if need be. |
That case makes a lot more sense, though I can't see a reason why the references aren't automatically freed by the native return (there's nothing special about that native). |
And yes, please do share your testcase - maybe there's something unexpected there that's exposing this issue. |
produced ~2k leak in a day's time for me. the leak can be seen through walking through the memory pools attached with the vmthread->jniLocalReferences. edit: the leaking objects found were of type |
Stepping through the JNI code performs as expected:
|
You mention |
ok - so you mean the
Yes, those are class references, not object references. So is it root caused by a different issue? |
I guess I addressed all the review comments, pls have a look @gacholio @keithc-ca ! |
db18294
to
7070f91
Compare
7070f91
to
4ae7f4c
Compare
02ce70d
to
a22141f
Compare
a22141f
to
39d1ace
Compare
Back to you @keithc-ca |
d6a9bd2
to
6094ead
Compare
runtime/jcl/common/mgmtthread.c
Outdated
goto initIDCache_fail; | ||
} | ||
if (!(gcls = (*env)->NewGlobalRef(env, cls))) { | ||
err = JNI_ENOMEM; | ||
goto initIDCache_fail; | ||
} | ||
(*env)->DeleteLocalRef(env, cls); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove all of these from this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
I think the PR title and description should be updated - the history of this change isn't really relevant. |
6094ead
to
8064369
Compare
the title is already modified to reflect the current change set. pls let me know if you think it can be improved. the description is changed to reflect the current change set, the history is removed. |
I question "streamline" when we're actually fixing a leak problem. Something more like "ensure JNI cache is initialized only once". |
Please update the commit message as well: this isn't a change to "streamline" anything. |
initIDCache is currently subjected for race condition, and if concurrently entered by multiple threads can result in reference leaks. Fix this by introducing a wrapper class that acts as a bridge, invoking the method from the static initializer of the wrapper class, so that the synchronization is taken care through class initializer.
8064369
to
bb97b1e
Compare
@keithc-ca - done |
Jenkins test sanity amac jdk17 |
initIDCache is currently subjected for race condition, and if concurrently entered by multiple threads can result in reference leaks. Streamline the cache population by invoking it from a class static initialiser which is inherently synchronised.