写点什么

深入虚拟机探索 Thread start

用户头像
4ye
关注
发布于: 1 小时前

Hi~ o( ̄▽ ̄)ブ ,小伙伴们早上好呀~


赶紧搭上 冒险家 4ye 的航船叭 ,一起来探索下 hotspot 源码大陆~ 😝


这期和大家带来了这个 线程 Thread start 的奥秘


Thread start 源码揭秘

public synchronized void start() {    /**     * This method is not invoked for the main method thread or "system"     * group threads created/set up by the VM. Any new functionality added     * to this method in the future may have to also be added to the VM.     *     * A zero status value corresponds to state "NEW".     */    if (threadStatus != 0)        throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ 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 */ } }}
复制代码


从上面的代码中,我们可以发现它会先去 判断 threadStatus 是不是 0, 不是的话会抛出异常 。


Thread 源码中我们可以发现 threadStatus 默认值就是 0



  • 那什么时候会被改变呢?

  • 这个 threadStatus 都有哪些值呢?


嘿嘿 带着小小的疑问继续往下看叭~ 😝



start0

状态的改变肯定伴随着线程的启动,所以我们直接来到下面这个 start0 方法


private native void start0();
复制代码


可以发现它是一个 native 函数,我们直接


OpenJDKJDK 源码中全局搜索,就可以找到它了


path: jdk\src\share\native\java\lang\Thread.c


JNINativeMethod

这个 JNINativeMethod 是一个结构体


path: jdk\src\share\javavm\export\jni.h


/* * used in RegisterNatives to describe native method name, signature, * and function pointer. */typedef struct {    char *name;    char *signature;    void *fnPtr;} JNINativeMethod;
复制代码

JVM_StartThread

顺着思路,我们找到这个 JVM_StartThread 方法


path: jdk\src\share\javavm\export\jvm.h


/* * java.lang.Thread */JNIEXPORT void JNICALLJVM_StartThread(JNIEnv *env, jobject thread);
复制代码


这里 JNIEXPORTJNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的


看到它是 JVM 开头的文件,我们来到下面这个 hotspot 的源码中找找看😋


发现这里有使用到👉


path: hotspot\src\share\vm\prims\jvm.cpp


代码:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))  JVMWrapper("JVM_StartThread");  JavaThread *native_thread = NULL;
// We cannot hold the Threads_lock when we throw an exception, // due to rank ordering issues. Example: we might need to grab the // Heap_lock while we construct the exception. bool throw_illegal_thread_state = false;
// We must release the Threads_lock before we can post a jvmti event // in Thread::start. { // Ensure that the C++ Thread and OSThread structures aren't freed before // we operate. MutexLocker mu(Threads_lock);
// Since JDK 5 the java.lang.Thread threadStatus is used to prevent // re-starting an already started thread, so we should usually find // that the JavaThread is null. However for a JNI attached thread // there is a small window between the Thread object being created // (with its JavaThread set) and the update to its threadStatus, so we // have to check for this if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { throw_illegal_thread_state = true; } else { // We could also check the stillborn flag to see if this thread was already stopped, but // for historical reasons we let the thread detect that itself when it starts running
jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); // Allocate the C++ Thread structure and create the native thread. The // stack size retrieved from java is signed, but the constructor takes // size_t (an unsigned type), so avoid passing negative values which would // result in really large stacks. size_t sz = size > 0 ? (size_t) size : 0; native_thread = new JavaThread(&thread_entry, sz);
// At this point it may be possible that no osthread was created for the // JavaThread due to lack of memory. Check for this situation and throw // an exception if necessary. Eventually we may want to change this so // that we only grab the lock if the thread was created successfully - // then we can also do this check and throw the exception in the // JavaThread constructor. if (native_thread->osthread() != NULL) { // Note: the current thread is not being used within "prepare". native_thread->prepare(jthread); } } }
if (throw_illegal_thread_state) { THROW(vmSymbols::java_lang_IllegalThreadStateException()); }
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) { // No one should hold a reference to the 'native_thread'. delete native_thread; if (JvmtiExport::should_post_resource_exhausted()) { JvmtiExport::post_resource_exhausted( JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS, "unable to create new native thread"); } THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "unable to create new native thread"); }
// 看这里~ Thread::start(native_thread);
JVM_END
复制代码


这里 JVM_ENTRY JVM_END是两个宏定义,定义了函数体的头和尾。 😄


这里太多细节了,啃不下呀 😅



讲几个大概的~ 咳咳 也就看看作者的注释翻译翻译🐷


比如:

创建线程

native_thread = new JavaThread(&thread_entry, sz);
复制代码
来到构造器中
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :  Thread()#if INCLUDE_ALL_GCS  , _satb_mark_queue(&_satb_mark_queue_set),  _dirty_card_queue(&_dirty_card_queue_set)#endif // INCLUDE_ALL_GCS{  if (TraceThreadEvents) {    tty->print_cr("creating thread %p", this);  }  initialize();  _jni_attach_state = _not_attaching_via_jni;  set_entry_point(entry_point);  // Create the native thread itself.  // %note runtime_23  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);  _safepoint_visible = false; }
复制代码
创建内核线程

发现这里是去 创建内核线程~ 👍


os::create_thread(this, thr_type, stack_sz);
复制代码


顺着思路继续探索~


针对 linux 的场景


有如下的代码 🐂


path: hotspot\src\os\linux\vm\os_linux.cpp


终于看到有点眼熟的函数了~ 哈哈哈


int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
复制代码



底层内容真是让人抓狂~ 无止境的.. 哈哈哈


这里截下这个指针函数 java_start ,有兴趣的小伙伴继续往下冲冲冲!😄


启动线程

另一个重点 **Thread::start(native_thread); **

Thread::start

来到下面这里~


path: hotspot\src\share\vm\runtime\thread.cpp



第一个红框的作用:


在启动该线程之前,将线程状态初始化为 RUNNABLE。

不能在线程启动后设置,因为我们不知道 正确的线程状态,它可能在 MONITOR_WAIT 或 在睡眠或其他状态。


第二个红框的作用,启动内核线程!


// The INITIALIZED state is distinguished from the SUSPENDED state because the// conditions in which a thread is first started are different from those in which// a suspension is resumed.  These differences make it hard for us to apply the// tougher checks when starting threads that we want to do when resuming them.// However, when start_thread is called as a result of Thread.start, on a Java// thread, the operation is synchronized on the Java Thread object.  So there// cannot be a race to start the thread and hence for the thread to exit while// we are working on it.  Non-Java threads that start Java threads either have// to do so in a context in which races are impossible, or should do appropriate// locking.
void os::start_thread(Thread* thread) { // guard suspend/resume MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag); OSThread* osthread = thread->osthread(); osthread->set_state(RUNNABLE); pd_start_thread(thread);}
复制代码


嘿嘿 这里就直接搜搜看 pd_start_thread 啦,毕竟一看就知道是它去启动线程的 哈哈哈 😝


结果如图~



最后记录下这个 全局函数 notify 😝


bool Monitor::notify() {  assert (_owner == Thread::current(), "invariant") ;  assert (ILocked(), "invariant") ;  if (_WaitSet == NULL) return true ;  NotifyCount ++ ;
// Transfer one thread from the WaitSet to the EntryList or cxq. // Currently we just unlink the head of the WaitSet and prepend to the cxq. // And of course we could just unlink it and unpark it, too, but // in that case it'd likely impale itself on the reentry. Thread::muxAcquire (_WaitLock, "notify:WaitLock") ; ParkEvent * nfy = _WaitSet ; if (nfy != NULL) { // DCL idiom _WaitSet = nfy->ListNext ; assert (nfy->Notified == 0, "invariant") ; // push nfy onto the cxq for (;;) { const intptr_t v = _LockWord.FullWord ; assert ((v & 0xFF) == _LBIT, "invariant") ; nfy->ListNext = (ParkEvent *)(v & ~_LBIT); if (CASPTR (&_LockWord, v, UNS(nfy)|_LBIT) == v) break; // interference - _LockWord changed -- just retry } // Note that setting Notified before pushing nfy onto the cxq is // also legal and safe, but the safety properties are much more // subtle, so for the sake of code stewardship ... OrderAccess::fence() ; nfy->Notified = 1; } Thread::muxRelease (_WaitLock) ; if (nfy != NULL && (NativeMonitorFlags & 16)) { // Experimental code ... light up the wakee in the hope that this thread (the owner) // will drop the lock just about the time the wakee comes ONPROC. nfy->unpark() ; } assert (ILocked(), "invariant") ; return true ;}
复制代码


惊喜,居然在这个代码中看到这段注释,这里提到 WaitSet , EntryList ,cxq


这是 隐式锁🔒 [[Synchronized 内部的实现原理呀]] 或者 称它为 [[Monitor 机制]] 👍


  • WaitSet 是一个等待队列,存放进入等待状态的线程

  • cxq 是一个竞争队列,所有请求锁🔒的线程会先到这里

  • EntryList 存放 cxq 中有资格成为候选资源去竞争锁的线程


// Transfer one thread from the WaitSet to the EntryList or cxq.
// Currently we just unlink the head of the WaitSet and prepend to the cxq.
// And of course we could just unlink it and unpark it, too, but
// in that case it'd likely impale itself on the reentry.
Thread::muxAcquire (_WaitLock, "notify:WaitLock") ;


嘿嘿 后面深入锁的部分再分享😝




threadStatus

回到最上面,那还剩一个问题没解决~


这个 threadStatus 都有哪些值呢?


这里我们可以从 jvm.h 源码中获取到 👇


/* * Java thread state support */enum {    JAVA_THREAD_STATE_NEW           = 0,    JAVA_THREAD_STATE_RUNNABLE      = 1,    JAVA_THREAD_STATE_BLOCKED       = 2,    JAVA_THREAD_STATE_WAITING       = 3,    JAVA_THREAD_STATE_TIMED_WAITING = 4,    JAVA_THREAD_STATE_TERMINATED    = 5,    JAVA_THREAD_STATE_COUNT         = 6};
复制代码

总结

本次的 Thread start 深入虚拟机探索之旅 就先告一段落啦~ 太难啃了🙃(咳咳,实力差太多了!被 Hotspot 源码 Boss 暴打一顿后,4ye 冒险家只能带着一点点经验跑开了ε=ε=ε=(~ ̄▽ ̄)~ 哈哈哈🐷 )


收获:

  1. 学了一点 C 和 C++ 语法 哈哈哈

  2. 了解到 线程创建的本质 ,针对 linux 场景,有如下的感悟

  3. 发现每个 java 线程,都会对应到一个内核线程 ;

  4. 可以更好地体会 用户态内核态 的存在 ;

  5. 了解到启动一个线程时,线程状态的改变是在开启线程之前

  6. 启动内核线程的实际操作是调用了 Monitor::notify 方法去唤醒它 👍

  7. 同时,在这个方法中,窥探到了一丝丝 Monitor 机制 的存在 ;

  8. 看到了无处不在的 mutex

  9. 更能体会到 java 为能一次编译到处运行了 哈哈, JVM 已经封装好了对应的 OS

  10. 源码阅读经验+1 哈哈哈

  11. 开启了新大陆 : 后面还可以尝试编译下,做一个自己的 JVM 😝


我是 4ye 我们下期再见啦~ 喜欢的小伙伴可以星标哦!

发布于: 1 小时前阅读数: 3
用户头像

4ye

关注

公众号:J a v a 4 y e 2021.07.19 加入

还未添加个人简介

评论

发布
暂无评论
深入虚拟机探索Thread start