Пересоздание JVM

From
Boris Rudakov (2:5054/9.4)
To
All (2:5054/9)
Date
2002-10-03T00:12:18Z
Area
RU.JAVA
Hello All!

Нарвался на странную засаду:

1. Есть хост-процесс (некий сервер), который стартует JVM и создает одну VM.
2. В некоторых случаях процесс решает что хочет выгрузить JVM и (иногда) загрузить ее снова.
3. JVM (dll которая) выгружаться не желает, хотя все вызова уничтожения VM возвращают что все тип-топ.
4. При повторном создании VM процесс валится где-то в недрах JVM в момент инициализации. Тот же самый код инициализации нормально отрабатывает если VM первая и единственная.

Проверял на JDK 1.3.x. Хост процесс написан на плюсах, компилляется и Борландом и Инвижуалом. Глюк с JVM происходит одинаково.

Теперь более подробно.

Код загрузки вкратце выглядит так:

TLibrary JVMLib;  // HLIBRARY wrapper class
JavaVM* JVM;      // Global process pointer to the VM

....
bool LOCALPROC InitJVM(JNIEnv** OutEnv)
{
  if (JVMLib == 0)
    LocateJVM(); // Get JVM path from registry or well known locations
    if (JVMName.Length() == 0) return false;
    JVMLib.Load(JVMName); // Wrapper around LoadLibrary
    if (JVMLib == 0) return false;
  }

  // Get JVM entry points
  jint (JNICALL *JNI_GetDefaultJavaVMInitArgs)(void *args);
  LOAD_PROC(JNI_GetDefaultJavaVMInitArgs); // Wrapper around GetprocAddress
  jint (JNICALL *JNI_CreateJavaVM)(JavaVM **pvm, void **penv, void *args);
  LOAD_PROC(JNI_CreateJavaVM);

  // Prepare startup arguments
  char ClassPath[1024];
  RtlZeroMemory(ClassPath,  sizeof(ClassPath));
  lstrcpy(ClassPath, "-Djava.class.path=");
  LPSTR CP = StrEnd(ClassPath);
  UINT L = CP - ClassPath;
  GetEnvironmentVariable("ClassPath", ClassPath + L, sizeof(ClassPath) - L);
  JavaVMInitArgs Args; Clear(Args);
  Args.version  = JNI_VERSION_1_2;
  JNI_GetDefaultJavaVMInitArgs(&Args);
  JavaVMOption Opt[] = {{ClassPath}};
  Args.nOptions = 1;
  Args.options = Opt;
  Args.ignoreUnrecognized = JNI_FALSE;

  // Start JVM
  JNIEnv *Env = 0;
  if (JNI_CreateJavaVM(&JVM, (void**)&Env, &Args) != 0) {
    JVMLib.Free();
    JVM = 0;
    return false;
  }

  // Call optional setup code
  jclass Cls = Env->FindClass(MainClass);
  if (Cls) {
    jmethodID SetupID = Env->GetStaticMethodID(Cls, "startup", "(I)V");
    if (SetupID)
      Env->CallStaticVoidMethod(Cls, SetupID, (jint)ECB);
    else {
      SetupID = Env->GetStaticMethodID(Cls, "startup", "()V");
      if (SetupID) Env->CallStaticVoidMethod(Cls, SetupID);
    }
  }
  Env->ExceptionClear();

  // Detach current thread or pass Env to the caller
  if (OutEnv) *OutEnv = Env;
  else JVM->DetachCurrentThread();
  return true;
}

Приведенный выше код нормально загружает JVM и инициализирует все что надо. Но - один раз :(
Вызывается все это дело внутри EnterCriticalSection, так что никаких косяков с многонитевостью (хост процесс многонитевый) абсолютно точно нет.

Все вызова JVM выглядят так:

....
  JNIEnv* Env = 0;
  if (!JVM) {
    TCriticalSectionLock L(JVMLock); // Wrapper around
                                     // Enter/LeaveCriticalSection
    if (!InitJVM(&Env)) return false;
  } else {
    JavaVMAttachArgs Args = {JNI_VERSION_1_2, "user"};
    JVM->AttachCurrentThread((void**)&Env, &Args);
    if (Env == 0) {
      ECB->dwHttpStatusCode = 500;
      lstrcpy(ECB->lpszLogData, "Failed to attach current thread to the JVM");
      JVM->DetachCurrentThread();
      return false;
    }
  }

  // Call service
  jclass Cls = Env->FindClass(MainClass);
....

  // Detach current thread and returns
  jthrowable E = Env->ExceptionOccurred();
  if (E) {
    Env->ExceptionClear();
...
  }
  JVM->DetachCurrentThread();
  return Result;
}
...

Вроде все чисто и тоже никаких косяков не наблюдается. Видно что JVM создается by demand, после отработки запроса нить детачится. Все выглядит правильно. И работает правильно. Но...

Вот код выгрузки JVM:

void LOCALPROC StopJVM(bool Soft, LPEXTENSION_CONTROL_BLOCK ECB)
{
  if (JVM) {
    //
    // In the soft mode we call server shutdown before termination
    //
    if (Soft) {
      JNIEnv* Env = 0;
      JavaVMAttachArgs Args = {JNI_VERSION_1_2, "user"};
      JVM->AttachCurrentThread((void**)&Env, &Args);
      if (Env) {
        jclass Cls = Env->FindClass(MainClass);
        if (Cls) {
          jmethodID StopID = 0;
          if (ECB) {
            StopID = Env->GetStaticMethodID(Cls, "shutdown", "(I)V");
            if (StopID) Env->CallStaticVoidMethod(Cls, StopID, (jint)ECB);
          }
          if (StopID == 0) { // Either ECB is null or no "(I)V" method
            StopID = Env->GetStaticMethodID(Cls, "shutdown", "()V");
            if (StopID) Env->CallStaticVoidMethod(Cls, StopID);
          }
        }
        Env->ExceptionClear();
      }
      JVM->DetachCurrentThread();
    }
    JVM->DestroyJavaVM();
    JVM = 0;
  }
  // FreeLibrary
[!!!]>
  JVMLib.Free();
}

Вызов этой штуки так же защищен CriticalSection и, кроме того, абсолютно точно не производится в один момент времени с работой других нитей (МОИХ нитей) внутри JVM.

Типа как бы якобы все работает правильно, НО:

1. Вот там где я написал "[!!!]>" JVM.dll даже и не думает выгружаться :(
Ежели для проверки в цикле завернуть несколько вызовов FreeLibrary подряд, то этак примерно на 5-ой итерации она выгружается-таки, но java.dll и пачка стандартных native libs (типа zip.dll и иже) выгружаться даже и не думают.

2. Не смотря на то что этот код вроде как-бы отрабатывает и все функции возвращают JNI_OK, последующая on-demand инициализация JVM валится при вызове JNI_CreateJavaVM где-то в недрах java.dll. Попытки (слабые) понять чего ей, заразе, не нравится ни к чему не привели.

Мне это категорически не нравится. Мне действительно надо уметь при желании в любой момент времени выгружать/загружать JVM.

Я начал подозревать что собака порылась в том, что JVM стартует еще и свои нити, например GarbageCollector. И моя нить, производящая шатдаун, конфликтует с ними. Впрочем, эту теорию я еще не проверял.

Кто-то что-то может сказать ?

Boris Rudakov,               Мама мне все всегда хорошо обьясняла,
BBR                          чтобы я все понял.

--- Be happy: BBR is looking at you !
 * Origin: АлкАголь малыми дозами безвреден в любых количествах (2:5054/9.4)