Welcome to my column:Half stack engineer

I spent some time over the weekend looking at how HotSpot creates Java threads. The source code cited in the article deleted many details, only retain the main process, interested students can go to their own in-depth research. Ability is limited, wrong place kindly correct.

Introduction to Java threads

Those of you who do Java development or Android are certainly familiar with Java threads. While reading some books on the JVM, I learned that Java threads actually map to the kernel threads of the operating system, so Java threads are basically managed by the operating system. In Linux, threads and processes are described using the same structure, but processes have their own separate address space, while threads of the same process share resources.

Two: Java thread entry analysis

There are two main ways to start a Java Thread. The first is to implement a subclass of Thread that overrides run (). The second method implements a Runnable that Thread executes. Both methods are easy to use, and we can choose according to our business needs. However, either method needs to be called thread.start () to actually start an asynchronous Thread to work. When I first got into Java in college, I naivedly thought that calling Thread.run () would be enough. When run () is called directly, the JVM does not create a thread. Run () only works on the existing thread, just like calling a method on an ordinary object.

As mentioned above, thread.start () must be called to start a Thread, so this method is our entry point.

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();Copy the code

As can be seen from the above code, the start () method does some work such as thread status judgment, but the real start of the Java thread is to call start0 (), start0 () is a Native method. Where is start0 () implemented? Let’s start by looking at a definition in 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},
};
Copy the code

JNINativeMethod: JNINativeMethod: JNINativeMethod: JNINativeMethod: JNINativeMethod

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

JNINativeMethod mainly carries out a mapping relationship of JNI methods and binds native methods to real implementation methods. So when exactly does it bind? The Java layer Thread called the registerNatives () method in the static initialization block of the class:

   private static native void registerNatives();
    static {
        registerNatives();
    }
Copy the code

RegisterNatives () corresponds to the Jni method:

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

As you can see, the method mapping in array Methods is registered.

Once registered, let’s look at the array defined above, which binds many native methods in Java threads to the method Pointers that implement their functions, such as start0 () and JVM_StartThread. So if we want to explore start0 (), we can simply look at the method pointed to by the JVM_StartThread pointer.

Three: Java thread creation

JVM_StartThread is defined in 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_ENDCopy the code

There were a lot of English annotations in the source code, I’m here to delete the first, step by step to analyze the above code.

1: Checks whether the Java thread has been started. If so, an exception will be thrown.

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

2: If the Java thread was not started in step 1, the Java thread will be created.

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);
Copy the code

The creation of a Java thread is primarily in the JavaThread constructor:

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);
}
Copy the code

OS ::create_thread(this, thr_type, stack_sz) creates the kernel thread corresponding to the Java thread.

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; }Copy the code

The above method basically uses pthread_create () to create a thread. The third parameter, thread_native_entry, is the initial running address of the new thread, which is a method pointer defined in os_bsd.cpp, and the fourth parameter, thread, is the thread_native_entry parameter:

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

Once created, the new thread will start running from thread_native_entry (), which calls thread->run () :

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

This method ends with a call to 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; }Copy the code

Let’s focus on this->entry_point()(this, this). Entry_point () returns the same thread_entry that was passed in new JavaThread(&thread_entry, sz). This is equivalent to calling thread_entry (this, this). Thread_entry is defined in 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);
}
Copy the code

Ha ha, here to meet an old friend: JavaCalls. The JavaCalls module is used to invoke Java methods. For those who don’t know, you can refer directly to the context of JVM Method execution.

Let’s look at some of the arguments passed in here to call JavaCalls::call_virtual () :

Obj: Java thread object;

KlassHandle(THREAD, SystemDictionary::Thread_klass()) : java_lang_Thread;

VmSymbols ::run_method_name() : “run”;

VmSymbols ::void_method_signature() : “V”;

After the above analysis, this is essentially the start of calling the Run () method of the Java thread object.

3: Start executing the created kernel thread, starting with thread_entry in step 2.

Thread::start(native_thread);
Copy the code

Four:

At this point, the Java thread is actually running. To summarize the above process:

1: Call the Java thread start () method, via JNI, to the JVM layer.

2: The JVM creates a system kernel thread with pthread_create () and specifies the initial running address of the kernel thread, which is a method pointer.

3: In the initial run method of the kernel thread, the JavaCalls module is used to call the Run () method of the Java thread to start java-level thread execution.

Welcome to my column:Half stack engineer