207fbcb083
Reviewed-by: sspitsyn, kevinw
209 lines
6.9 KiB
C++
209 lines
6.9 KiB
C++
/*
|
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
#include <jni.h>
|
|
#include <jvmti.h>
|
|
#include <jvmti_common.h>
|
|
#include <atomic>
|
|
#include <string.h>
|
|
|
|
namespace {
|
|
|
|
jvmtiEnv *jvmti = nullptr;
|
|
|
|
const int TAG_START = 100;
|
|
|
|
struct RefCounters {
|
|
jint test_class_count;
|
|
jint *count;
|
|
jlong *thread_id;
|
|
|
|
RefCounters(): test_class_count(0), count(nullptr) {}
|
|
|
|
void* alloc(JNIEnv* env, jlong size) {
|
|
unsigned char* ptr;
|
|
jvmtiError err = jvmti->Allocate(size, &ptr);
|
|
if (err != JVMTI_ERROR_NONE) {
|
|
env->FatalError("jvmti->Allocate failed");
|
|
}
|
|
memset(ptr, 0, size);
|
|
return ptr;
|
|
}
|
|
|
|
void init(JNIEnv* env, jint test_class_count) {
|
|
this->test_class_count = test_class_count;
|
|
count = (jint*)alloc(env, sizeof(count[0]) * test_class_count);
|
|
thread_id = (jlong*)alloc(env, sizeof(thread_id[0]) * test_class_count);
|
|
}
|
|
} refCounters;
|
|
|
|
}
|
|
|
|
/////////////////////////////////////////
|
|
// Agent functions
|
|
/////////////////////////////////////////
|
|
jint JNICALL
|
|
HeapReferenceCallback(jvmtiHeapReferenceKind reference_kind,
|
|
const jvmtiHeapReferenceInfo* reference_info,
|
|
jlong class_tag, jlong referrer_class_tag, jlong size,
|
|
jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data) {
|
|
if (class_tag >= TAG_START) {
|
|
jlong index = class_tag - TAG_START;
|
|
switch (reference_kind) {
|
|
case JVMTI_HEAP_REFERENCE_STACK_LOCAL: {
|
|
jvmtiHeapReferenceInfoStackLocal *stackInfo = (jvmtiHeapReferenceInfoStackLocal *)reference_info;
|
|
refCounters.count[index]++;
|
|
refCounters.thread_id[index] = stackInfo->thread_id;
|
|
LOG("Stack local: index = %d, thread_id = %d\n",
|
|
(int)index, (int)stackInfo->thread_id);
|
|
if (refCounters.count[index] > 1) {
|
|
LOG("ERROR: count > 1: %d\n", (int)refCounters.count[index]);
|
|
}
|
|
}
|
|
break;
|
|
case JVMTI_HEAP_REFERENCE_JNI_LOCAL: {
|
|
jvmtiHeapReferenceInfoJniLocal *jniInfo = (jvmtiHeapReferenceInfoJniLocal *)reference_info;
|
|
refCounters.count[index]++;
|
|
refCounters.thread_id[index] = jniInfo->thread_id;
|
|
LOG("JNI local: index = %d, thread_id = %d\n",
|
|
(int)index, (int)jniInfo->thread_id);
|
|
if (refCounters.count[index] > 1) {
|
|
LOG("ERROR: count > 1: %d\n", (int)refCounters.count[index]);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// unexpected ref.kind
|
|
LOG("ERROR: unexpected ref_kind for class %d: %d\n",
|
|
(int)index, (int)reference_kind);
|
|
}
|
|
}
|
|
return JVMTI_VISIT_OBJECTS;
|
|
}
|
|
|
|
extern "C" JNIEXPORT jint JNICALL
|
|
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
|
|
if (vm->GetEnv((void **)&jvmti, JVMTI_VERSION) != JNI_OK) {
|
|
LOG("Could not initialize JVMTI\n");
|
|
return JNI_ERR;
|
|
}
|
|
jvmtiCapabilities capabilities;
|
|
memset(&capabilities, 0, sizeof(capabilities));
|
|
capabilities.can_tag_objects = 1;
|
|
jvmtiError err = jvmti->AddCapabilities(&capabilities);
|
|
if (err != JVMTI_ERROR_NONE) {
|
|
LOG("JVMTI AddCapabilities error: %d\n", err);
|
|
return JNI_ERR;
|
|
}
|
|
|
|
return JNI_OK;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////
|
|
// Test native methods
|
|
/////////////////////////////////////////
|
|
extern "C" JNIEXPORT void JNICALL
|
|
Java_VThreadStackRefTest_test(JNIEnv* env, jclass clazz, jobjectArray classes) {
|
|
jsize classes_count = env->GetArrayLength(classes);
|
|
for (int i = 0; i < classes_count; i++) {
|
|
jvmti->SetTag(env->GetObjectArrayElement(classes, i), TAG_START + i);
|
|
}
|
|
refCounters.init(env, classes_count);
|
|
jvmtiHeapCallbacks callbacks;
|
|
memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
|
|
callbacks.heap_reference_callback = HeapReferenceCallback;
|
|
jvmtiError err = jvmti->FollowReferences(0, nullptr, nullptr, &callbacks, nullptr);
|
|
if (err != JVMTI_ERROR_NONE) {
|
|
LOG("JVMTI FollowReferences error: %d\n", err);
|
|
env->FatalError("FollowReferences failed");
|
|
}
|
|
}
|
|
|
|
extern "C" JNIEXPORT jint JNICALL
|
|
Java_VThreadStackRefTest_getRefCount(JNIEnv* env, jclass clazz, jint index) {
|
|
return refCounters.count[index];
|
|
}
|
|
|
|
extern "C" JNIEXPORT jlong JNICALL
|
|
Java_VThreadStackRefTest_getRefThreadID(JNIEnv* env, jclass clazz, jint index) {
|
|
return refCounters.thread_id[index];
|
|
}
|
|
|
|
static void print_created_class(JNIEnv* env, jclass cls) {
|
|
jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");
|
|
if (mid == nullptr) {
|
|
env->FatalError("failed to get toString method");
|
|
return;
|
|
}
|
|
jstring jstr = (jstring)env->CallObjectMethod(cls, mid);
|
|
const char* str = env->GetStringUTFChars(jstr, 0);
|
|
LOG("created %s\n", str);
|
|
env->ReleaseStringUTFChars(jstr, str);
|
|
}
|
|
|
|
// Creates object of the the specified class (local JNI)
|
|
// and calls the provided callback.
|
|
extern "C" JNIEXPORT void JNICALL
|
|
Java_VThreadStackRefTest_createObjAndCallback(JNIEnv* env, jclass clazz, jclass cls, jobject callback) {
|
|
jobject jobj = env->AllocObject(cls);
|
|
print_created_class(env, cls);
|
|
|
|
jclass callbackClass = env->GetObjectClass(callback);
|
|
jmethodID mid = env->GetMethodID(callbackClass, "run", "()V");
|
|
if (mid == nullptr) {
|
|
env->FatalError("cannot get run method");
|
|
return;
|
|
}
|
|
env->CallVoidMethod(callback, mid);
|
|
}
|
|
|
|
static std::atomic<bool> time_to_exit(false);
|
|
|
|
// Creates object of the the specified class (local JNI),
|
|
// sets mountedVthreadReady static field,
|
|
// and then waits until endWait() method is called.
|
|
extern "C" JNIEXPORT void JNICALL
|
|
Java_VThreadStackRefTest_createObjAndWait(JNIEnv* env, jclass clazz, jclass cls) {
|
|
jobject jobj = env->AllocObject(cls);
|
|
print_created_class(env, cls);
|
|
|
|
// Notify main thread that we are ready
|
|
jfieldID fid = env->GetStaticFieldID(clazz, "mountedVthreadReady", "Z");
|
|
if (fid == nullptr) {
|
|
env->FatalError("cannot get mountedVthreadReady field");
|
|
return;
|
|
}
|
|
env->SetStaticBooleanField(clazz, fid, JNI_TRUE);
|
|
|
|
while (!time_to_exit) {
|
|
sleep_ms(100);
|
|
}
|
|
}
|
|
|
|
// Signals createObjAndWait() to exit.
|
|
extern "C" JNIEXPORT void JNICALL
|
|
Java_VThreadStackRefTest_endWait(JNIEnv* env, jclass clazz) {
|
|
time_to_exit = true;
|
|
}
|