Процесс запуска Java-потока JVM

Java JVM

Добро пожаловать в мою колонку:полустэк инженер

На выходных я потратил некоторое время, чтобы изучить, как HotSpot создает потоки Java, и, кстати, подвел итоги. Многие детали удалены из исходного кода, цитируемого в статье, и сохранен только основной процесс.Заинтересованные студенты могут подробно изучить его самостоятельно. Возможности ограничены, поправьте меня, если я ошибаюсь.

A: Java Threats Введение

Делайте Java Development или одноклассники, обязательно знакомы с нитями Java. Когда вы читаете некоторые книги, связанные с JVM, вы узнали, что нить Java фактически отображается с нитью ядра операционной системы, поэтому резьба Java в основном используется операционная система. В Linux потоки и процессы описаны в той же структуре, но процесс имеет свое собственное независимое адресное пространство, и между тем же процессом передается между нитью.

Второй: анализ входа в поток Java

Существует два основных способа запуска Java-потока: первый — реализовать подкласс, наследуемый от Thread, и переопределить run(), второй — реализовать Runnable и передать его Thread для выполнения. Эти два метода очень просты, мы можем выбрать в соответствии с потребностями нашего бизнеса, но независимо от того, какой метод мы выберем, нам нужно вызвать Thread.start(), чтобы действительно запустить асинхронный поток для работы. Раньше я думал, что вызов Thread.run() было достаточно, ведь вызывая run() напрямую, JVM не будет создавать поток, а run() будет работать только с исходным потоком, что ничем не отличается от вызова метода обычных объектов.

Как упоминалось выше, чтобы запустить поток, необходимо вызвать Thread.start(), поэтому этот метод является отправной точкой для нашего исследования.

   public synchronized void start() {
        
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

Из приведенного выше кода мы можем знать, что некоторая оценка состояния потока и другая работа выполняются в методе start(), но место, где фактически запускается поток Java, — это вызов start0(), который является собственным методом. Где реализован start0()? Давайте сначала посмотрим на определение в Thread.c:

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

Массив определен выше, а структурная переменная типа JNINativeMethod хранится в массиве JNINativeMethod определен в jni.h:

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

JNINativeMethod в основном предназначен для выполнения отношения отображения метода jni и привязки собственного метода к реальному методу реализации. Итак, когда именно он связан? Поток уровня java вызывает метод registerNatives() в статическом блоке инициализации класса:

   private static native void registerNatives();
    static {
        registerNatives();
    }

Давайте взглянем на метод Jni, соответствующий функции registerNatives():

JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

Можно видеть, что в это время будут зарегистрированы отношения сопоставления методов в вышеупомянутых методах массива.

После регистрации давайте взглянем на определенный выше массив, который связывает многие нативные методы в потоках Java с указателями методов, реализующими их функции, такими как start0() и JVM_StartThread. Итак, мы хотим изучить start0(), просто взгляните на метод, на который указывает указатель JVM_StartThread.

Три: создание потока Java

JVM_StartThread определен в jvm.cpp:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
  bool throw_illegal_thread_state = false;

  {

    MutexLocker mu(Threads_lock);
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
    
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);
      if (native_thread->osthread() != NULL) {
        native_thread->prepare(jthread);
      }
    }
  }
  
  ......
  Thread::start(native_thread);

JVM_END

В исходном коде много англоязычных комментариев, здесь я их удалю, давайте пошагово разберем приведенный выше код.

1: Судя по тому, запущен ли поток Java, если он был запущен, он будет брошен.

if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
 }

2: Если поток Java не был запущен в первом решении, он начнет создавать поток Java.

jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);

Процесс создания потоков Java в основном находится в конструкторе JavaThread:

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
                       Thread()
{
  initialize();
  _jni_attach_state = _not_attaching_via_jni;
  set_entry_point(entry_point);
  os::ThreadType thr_type = os::java_thread;
  thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
                                                     os::java_thread;
  os::create_thread(this, thr_type, stack_sz);
}

Последнее предложение os::create_thread(this, thr_type, stack_sz) начинает фактически создавать поток ядра, соответствующий потоку Java.

bool os::create_thread(Thread* thread, ThreadType thr_type,
                       size_t req_stack_size) {
    ......
    pthread_t tid;
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);
    ......
    return true;
}

Вышеупомянутый метод в основном использует pthread_create() для создания потоков. Третий параметр thread_native_entry — это начальный адрес нового запущенного потока, который представляет собой указатель на метод, определенный в os_bsd.cpp, а четвертый параметр thread — это параметр thread_native_entry:

static void *thread_native_entry(Thread *thread) {
  ......
  thread->run();
  ......
  return 0;
}

После того, как новый поток будет создан, он запустится из thread_native_entry(), а thread->run() будет вызван в thread_native_entry():

// thread.cpp
void JavaThread::run() {
  ......
  thread_main_inner();
}

Этот метод вызывает thread_main_inner() в конце:

// thread.cpp
void JavaThread::thread_main_inner() {
  if (!this->has_pending_exception() &&
      !java_lang_Thread::is_stillborn(this->threadObj())) {
    {
      ResourceMark rm(this);
      this->set_native_thread_name(this->get_thread_name());
    }
    HandleMark hm(this);
    this->entry_point()(this, this);
  }
  DTRACE_THREAD_PROBE(stop, this);
  this->exit(false);
  delete this;
}

Наш фокус на этом> intron_point () (это, this), entry_point () возвращается фактически в новом javathread (& thread_entry, sz), когда thread_entry. Вот эквивалентно призвать Thread_entry (это, это). Thread_entry определен в JVM.cpp в:

// jvm.cpp
static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

Ха-ха, встретил здесь старого друга: JavaCalls. Модуль JavaCalls используется для вызова методов Java. Для тех, кто не знает, вы можете непосредственно прочитать статьи, написанные ранее«Все плюсы и минусы выполнения метода JVM».

Давайте посмотрим на некоторые параметры, переданные для вызова JavaCalls::call_virtual() здесь:

цель:Объект Java Thread;

KlassHandle(THREAD, SystemDictionary::Thread_klass()):класс потока Java, записанный в SystemDictionary, а именно java_lang_Thread;

vmSymbols::run_method_name():то есть «бежать»;

vmSymbols::void_method_signature():Это "()V";

После вышеприведенного анализа здесь фактически начинается вызов метода run() объекта потока Java.

3: Начать выполнение созданного потока ядра, то есть начать выполнение с thread_entry, упомянутого во втором шаге.

Thread::start(native_thread);

Четыре: Резюме

На данный момент поток Java фактически запущен.Обобщим описанный выше процесс:

1: вызовите метод start() потока Java и вызовите его на уровень JVM с помощью метода jni.

2: JVM создает системный поток ядра с помощью pthread_create() и указывает начальный рабочий адрес потока ядра, то есть указатель метода.

3: В исходном методе запуска резьбы ядра используется использование модуля JavaCalls, вызывая метод Java Thread () для начать реализацию нитей Java.

Добро пожаловать в мою колонку:полустэк инженер