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
函数,我们直接
OpenJDK 的 JDK
源码中全局搜索,就可以找到它了
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 JNICALL
JVM_StartThread(JNIEnv *env, jobject thread);
复制代码
这里 JNIEXPORT
和 JNICALL
都是 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 冒险家只能带着一点点经验跑开了ε=ε=ε=(~ ̄▽ ̄)~ 哈哈哈🐷 )
收获:
学了一点 C 和 C++ 语法 哈哈哈
了解到 线程创建的本质 ,针对 linux
场景,有如下的感悟
发现每个 java 线程,都会对应到一个内核线程 ;
可以更好地体会 用户态 和 内核态 的存在 ;
了解到启动一个线程时,线程状态的改变是在开启线程之前 ;
启动内核线程的实际操作是调用了 Monitor::notify 方法去唤醒它 👍
同时,在这个方法中,窥探到了一丝丝 Monitor 机制 的存在 ;
看到了无处不在的 mutex
;
更能体会到 java 为能一次编译到处运行了 哈哈, JVM 已经封装好了对应的 OS
源码阅读经验+1 哈哈哈
开启了新大陆 : 后面还可以尝试编译下,做一个自己的 JVM 😝
我是 4ye 我们下期再见啦~ 喜欢的小伙伴可以星标哦!
评论