寫在前面
寫作時間:2017.5
本文JDK版本:JDK 1.8
本文簡述:從Java的新建一個線程開始,溯源到Thread類的源碼浙巫,然后再從Thread類的源碼跳到虛擬機(jī)層的C+ +源碼莽鸭。虛擬機(jī)層是平臺相關(guān)的吗伤,但我沒有對每個平臺的實(shí)現(xiàn)機(jī)制都探索一遍,于是只選取Linux部分的實(shí)現(xiàn)源碼進(jìn)行探究蒋川。而C++的代碼在邏輯上又分為幾層牲芋,逐層深挖,一直到C++新建的線程調(diào)用Java線程的run()方法結(jié)束捺球。(本文每處都貼有源碼地址缸浦,可作參考)
本文持續(xù)更新地址:http://www.reibang.com/p/3ce1b5e5a55e
附:
本文相當(dāng)長,可以先看結(jié)尾的總結(jié)再回頭看正文氮兵,如果你只想理解個大概裂逐,也可以只看java部分以及結(jié)尾總結(jié)。
可能我的文筆以及實(shí)力都有所欠缺泣栈,想要完全理解的話可能需要多看幾遍以及花點(diǎn)時間卜高,請諸君包涵。(但做學(xué)問需要的不正是沉得住氣南片,耐得住寂寞嗎)
然而掺涛,代碼細(xì)節(jié)實(shí)在是太多了,硬吃實(shí)在不是個辦法疼进,所以我的建議是薪缆,最開始可以先不看代碼,只看每個小節(jié)后面的總結(jié)和代碼中的中文注釋伞广,甚至是先看流程終結(jié)拣帽,再看小節(jié)的總結(jié)疼电,最后有興趣才開始研究源碼。
切忌“只見樹木减拭,不見森林”
線程的創(chuàng)建
故事的開始蔽豺,是這樣的一段的代碼:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
thread.start();
然后我們的目標(biāo)就是,在C++源碼中找到這個run()方法是在哪里執(zhí)行的拧粪。
首先進(jìn)入的是它的構(gòu)造方法:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
經(jīng)過一系列的調(diào)用修陡,最終跳到這個方法,(這里的代碼有點(diǎn)長既们,就不給出完整代碼濒析,完整的源碼在這里:源碼傳送門)
/**
* Initializes a Thread.
*
* @param g 線程組
* @param target 攜帶run方法的Runnable對象
* @param name 線程名
* @param stackSize 新線程的需要的stack size,若此值為0則表明此屬性忽略
* @param acc 將要被繼承的AccessControlContext啥纸,若此值為null号杏,則此屬性默認(rèn)設(shè)置為
* AccessController.getContext()
* @param inheritThreadLocals 若此值為true,從構(gòu)造此線程的線程中繼承thread-locals的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.target = target;
setPriority(priority);
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
稍微嘗試著總結(jié)一下:
- 設(shè)置線程名(即設(shè)置name屬性)斯棒,一般情況下盾致,這個name的格式是“Thread-X”,X是一個數(shù)字,代表用這個線程類初始化的第X個實(shí)例
- 設(shè)置線程的ThreadGroup荣暮,這個ThreadGroup要么從應(yīng)用的SecurityManager處拿到庭惜,要么就直接繼承父線程的ThreadGroup。(設(shè)置完成以后還會有一個驗(yàn)證)
- 在真正設(shè)置此線程的ThreadGroup之前穗酥,需要為這個ThreadGroup的“還未開始的線程數(shù)目”(nUnstartedThreads)加一
- 設(shè)置此線程是否為守護(hù)線程(即設(shè)置daemon屬性)护赊,若父線程為守護(hù)線程,那么此線程也是守護(hù)線程
- 設(shè)置此線程的優(yōu)先級(即priority屬性)為父線程的優(yōu)先級(順便補(bǔ)充一句砾跃,子線程的優(yōu)先級是不能大于父線程的)
- 設(shè)置此線程的上下文類加載器
- 設(shè)置這個Thread的target骏啰,這個target就是攜帶了我們寫好的run()方法的Runnable對象
- 設(shè)置stackSize
- 設(shè)置線程ID(tid屬性)
關(guān)于run方法的一個補(bǔ)充
或許有人會說,我創(chuàng)建線程的時候抽高,是使用傳遞一個Runnable對象來進(jìn)行創(chuàng)建對象判耕,并沒有重寫原來的run方法,那么到了這里翘骂,還調(diào)用run方法的話壁熄,不就出問題了。
其實(shí)碳竟,這個問題我們看一下Thread.java的run()方法就好了:
@Override
public void run(){
if(target != null){
target.run();
}
}
target是什么草丧,target就是我們在初始化Thread時傳遞進(jìn)去的Runnable對象,那么這幾行代碼可以解釋我們的幾個疑問:
- 如果我們是通過傳遞Runnable對象來初始化Thread實(shí)例的話莹桅,那么就是將實(shí)例的run方法“替換”為Runnable對象的run()方法(邏輯上的替換)
- 如果我們的Thread繼承類重寫了run()方法昌执,也就是說原本的“替換”邏輯被覆蓋了,這時我們再通過傳遞Runnable對象來初始化Thread實(shí)例的話,這個實(shí)例的run不會再被替換仙蚜,也就是這個實(shí)例僅會執(zhí)行我們重寫的run方法。
線程的開始
我們都知道厂汗,開始一個線程就是調(diào)用這個線程的start方法:
/**
* 調(diào)用此方法將會使得thread開始運(yùn)行委粉,JVM會在這個線程中調(diào)用run方法。
* <p>
* 調(diào)用結(jié)果是娶桦,兩個線程同時運(yùn)行:調(diào)用此線程的線程贾节,以及此線程
* <p>
* 無論在什么情況下,要求同一條線程開始兩次都是不合法衷畦,即便是此線程已經(jīng)結(jié)束在要求開始也是不合法的栗涂。
*
* @exception IllegalThreadStateException 若此線程曾經(jīng)開始(start)過
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
*
* 對于main方法線程(即運(yùn)行main方法的線程),或者是由虛擬機(jī)創(chuàng)建的“system”組的
* 線程祈争,都不需要調(diào)用此方法來啟動斤程。
* 任何在這個方法中添加的新功能,以后都有可能添加到虛擬機(jī)上菩混。
* 翻譯的不是很滿意忿墅,原文留在這里:
* 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.
*
* 若線程的threadStatus為0,則表明這個狀態(tài)是NEW狀態(tài)
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/*
* 通知線程組:此線程已經(jīng)就緒沮峡,可以開始運(yùn)行了
* 所以疚脐,這條線程可以添加到線程組的線程list中,以及線程組
* 未開始線程的數(shù)目(he group's unstarted count)可以減一了邢疙。
*
* */
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 */
}
}
}
挺簡短的一段代碼棍弄,我們可以總結(jié)出以下步驟:
第一步,非常重要疟游,就是先檢查線程的狀態(tài)呼畸,如果線程的狀態(tài)不是NEW狀態(tài)的話,那么就會拋出一個異常(關(guān)于Java線程的狀態(tài)乡摹,可以看這篇文章:Java 線程的幾種狀態(tài))
第二步役耕,將這個Thread添加到之前引用的ThreadGroup中
第三步,調(diào)用native start0()
關(guān)于Thread類中的native方法
在Thread.java里面聪廉,有一個registerNatives()的native方法瞬痘,并且在Threa.java的第一個static塊中就調(diào)用了這個方法,保證這個方法在類加載中是第一個被調(diào)用的方法板熊。這個native方法的作用是為其他native方法注冊到JVM中(可見:JVM查找java native方法的規(guī)則)框全。
通過這個方法,我們可以找到Thrad.java中native方法在JVM中對應(yīng)方法:
Thread.c$Java_java_lang_Thread_registerNatives
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},
};
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
0干签、在進(jìn)入C之前的準(zhǔn)備
我們將會需要使用到這幾個概念:
java.lang.Thread: 這個是Java語言里的線程類津辩,由這個Java類創(chuàng)建的instance都會 1:1 映射到一個操作系統(tǒng)的osthread;
JavaThread: JVM中C++定義的類,一個JavaThread的instance代表了在JVM中的java.lang.Thread的instance, 它維護(hù)了線程的狀態(tài),并且維護(hù)一個指針指向java.lang.Thread創(chuàng)建的對象(oop)喘沿。它同時還維護(hù)了一個指針指向?qū)?yīng)的OSThread闸度,來獲取底層操作系統(tǒng)創(chuàng)建的osthread的狀態(tài)
OSThread: JVM中C++定義的類,代表了JVM中對底層操作系統(tǒng)的osthread的抽象蚜印,它維護(hù)著實(shí)際操作系統(tǒng)創(chuàng)建的線程句柄handle莺禁,可以獲取底層osthread的狀態(tài)
VMThread: JVM中C++定義的類,這個類和用戶創(chuàng)建的線程無關(guān)窄赋,是JVM本身用來進(jìn)行虛擬機(jī)操作的線程哟冬,比如GC
(引自:聊聊JVM(五)從JVM角度理解線程)
補(bǔ)充:需要注意的是osthread和OSThread的區(qū)別,簡單的說忆绰,osthread是操作系統(tǒng)級別的線程浩峡,也就是真正意義上的線程,它的具體實(shí)現(xiàn)由操作系統(tǒng)完成错敢;
而OSThread則是關(guān)于osthread的抽象翰灾,通過操作系統(tǒng)暴露出來的接口(句柄)對osthread進(jìn)行操作,在實(shí)際的運(yùn)行中稚茅,OSThread會根據(jù)所運(yùn)行的操作系統(tǒng)(平臺)來創(chuàng)建预侯,也就是說,如果字節(jié)碼運(yùn)行的平臺是Linux的話峰锁,那么創(chuàng)建出來的OSThread就是Linux的OSThread萎馅;如果是Windows的話,那么創(chuàng)建出來的就是Windows的OSThread
0.5虹蒋、流程概述
為了避免“只見樹木糜芳,不見森林”,特意將流程總結(jié)提前到開始
撇開源碼魄衅,整個過程大概是:
- 在Java中峭竣,使用java.lang.Thread的構(gòu)造方法來構(gòu)建一個java.lang.Thread對象,此時只是對這個對象的部分字段(例如線程名晃虫,優(yōu)先級等)進(jìn)行初始化皆撩;
- 調(diào)用java.lang.Thread對象的start()方法,開始此線程哲银。此時扛吞,在start()方法內(nèi)部,調(diào)用start0() 本地方法來開始此線程荆责;
- start0()在VM中對應(yīng)的是JVM_StartThread滥比,也就是,在VM中做院,實(shí)際運(yùn)行的是JVM_StartThread方法(宏),在這個方法中盲泛,創(chuàng)建了一個JavaThread對象濒持;
- 在JavaThread對象的創(chuàng)建過程中,會根據(jù)運(yùn)行平臺創(chuàng)建一個對應(yīng)的OSThread對象寺滚,且JavaThread保持這個OSThread對象的引用柑营;
- 在OSThread對象的創(chuàng)建過程中,創(chuàng)建一個平臺相關(guān)的底層級線程村视,如果這個底層級線程失敗由境,那么就拋出異常;
- 在正常情況下蓖议,這個底層級的線程開始運(yùn)行,并執(zhí)行java.lang.Thread對象的run方法讥蟆;
- 當(dāng)java.lang.Thread生成的Object的run()方法執(zhí)行完畢返回后勒虾,或者拋出異常終止后,終止native thread;
- 最后就是釋放相關(guān)的資源(包括內(nèi)存瘸彤、鎖等)
在上述過程修然,穿插著各種的判斷檢測,其中很大一部分都是關(guān)于各種層次下的線程的狀態(tài)的檢測质况,在JVM中愕宋,無論哪種層次的線程,都只允許執(zhí)行一次结榄。
1中贝、從Java進(jìn)入C:JVM_StartThread
首先JVM會進(jìn)入jvm.cpp的jvm.cpp$JVM_StartThread 方法(確切地說,這個JVM_StartThread是一個宏):
關(guān)于native的函數(shù)參數(shù):
第一個參數(shù):JNIEnv* 是定義任意native函數(shù)的第一個參數(shù)(包括調(diào)用JNI的RegisterNatives函數(shù)注冊的函數(shù))臼朗,指向JVM函數(shù)表的指針邻寿,函數(shù)表中的每一個入口指向一個JNI函數(shù),每個函數(shù)用于訪問JVM中特定的數(shù)據(jù)結(jié)構(gòu)视哑。
第二個參數(shù):調(diào)用java中native方法的實(shí)例或Class對象绣否,如果這個native方法是實(shí)例方法,則該參數(shù)是jobject挡毅,如果是靜態(tài)方法蒜撮,則是jclass
接下來我們來看下這個JVM_StartThread方法:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
// We must release the Threads_lock before we can post a jvmti event
// in Thread::start.
{
//在我們操作的時候,確保C++線程和OSThread不會被提前釋放
//(譯者附:)從上面的一個左花括號開始跪呈,一直到下一個匹配的右花括號為止段磨,都上了關(guān)于Threads_lock的互斥鎖
//可以理解為Java的sync塊
MutexLocker mu(Threads_lock);
// 安全原因,先進(jìn)行一次檢測耗绿,個人猜測是薇溃,判斷這個線程是否已被啟動過,若是已啟動過缭乘,拋線程狀態(tài)異常
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));
// 為C++ thread結(jié)構(gòu)體分配內(nèi)存沐序,以及創(chuàng)建一個本地線程琉用。這個從Java取過來的stack size是有一個符號數(shù),
// 但構(gòu)造器需要的size_t是一個無符號數(shù)策幼,所以在轉(zhuǎn)換的時候需要避免因?yàn)閟ize是負(fù)數(shù)出現(xiàn)的問題邑时。
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);
// 創(chuàng)建一個JavaThrea對象(創(chuàng)建過程見第二節(jié)),其中第二個參數(shù)thread_entry是一個方法指針特姐,詳見本節(jié)下方補(bǔ)充
// ***************** 注意這個thread_entry ************************
// ***************** 注意這個thread_entry ************************
// ***************** 注意這個thread_entry ************************
// 仍做一次安全檢測晶丘,因?yàn)閛sthread不一定能創(chuàng)建成功(詳見下文第三節(jié)的osthread創(chuàng)建)
if (native_thread->osthread() != NULL) {
// 注意:當(dāng)前線程在下面的“prepare”沒有被使用到。
native_thread->prepare(jthread);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
//事實(shí)上唐含,如果native_thread的osthread為NULL浅浮,就可以宣告整個線程創(chuàng)建過程失敗
//這時應(yīng)該整理內(nèi)存,并通知java層線程創(chuàng)建失敗
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
關(guān)于thread_entry方法的補(bǔ)充:注意捷枯,這個方法非常重要滚秩,后面還會出現(xiàn),所以我們留到后面再解釋【第6節(jié)】淮捆。
然后我們來看一下JVM在這個宏里面做了什么:
- 申請一個鎖
- 創(chuàng)建一個JavaThread對象【見第二節(jié)】
- 如果有異常情況郁油,拋出異常
- 對第二步的JavaThread類,做一個準(zhǔn)備操作:thread.cpp$JavaThread::prepare
- 調(diào)用Thread::start方法攀痊,讓一條線程開始運(yùn)行
2桐腌、JavaThread對象的創(chuàng)建
本節(jié)是接上面小節(jié)中間,關(guān)于JavaThread對象創(chuàng)建的講述的苟径。
JavaThread是Java層的線程與平臺層線程中間的一個過渡:
一個java.lang.Thread對象案站,在其start方法中,會創(chuàng)建一個JavaThread對象棘街,由于java.lang.Thread對象的start方法只允許調(diào)用一次嚼吞,所以java.lang.Thread對象與JavaThread對象是一一對應(yīng)的
而在JavaThread對象的創(chuàng)建過程,會創(chuàng)建一個OSThread對象蹬碧,并且JavaThread對象會持有一個指向該OSThread對象的指針(至于什么是OSThread舱禽,大概是平臺相關(guān)的系統(tǒng)層線程,更詳細(xì)見第三節(jié))
thread.cpp$JavaThread::JavaThread :
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);
// 在這里恩沽,由于內(nèi)存不足的原因誊稚,_osthread有可能為NULL、
// 而我們應(yīng)當(dāng)拋出OutOfMenoryError罗心,然而現(xiàn)在我們還不能拋出Error
// 因?yàn)榇朔椒ǖ恼{(diào)用者有可能扔持有著所有的鎖里伯,而我們必須釋放所有的鎖才能拋出后異常
// (拋出異常的工作包括:創(chuàng)建并初始化一個exception對象,而初始化exception對象則必須要通過JavaCall離開VM渤闷,
// 然后所有的鎖都必須要被釋放)
//
// 此時這條線程扔處在掛起的狀態(tài)疾瓮,而線程必須由創(chuàng)建者顯式地啟動!
// 此外飒箭,線程還必須顯式地通過Threads:add方法被添加到Threads list 中狼电。
// 上面的工作(顯式啟動蜒灰,顯式添加)之所以到達(dá)這里還沒完成,是因?yàn)榫€程還沒有被完成地初始化(可參見JVM_Start)
}
完成的操作有:
- 調(diào)用initialize方法對JavaThread進(jìn)行初始化肩碟,此方法僅是對結(jié)構(gòu)體的屬性做賦值强窖;
- 為這個JavaThread設(shè)置一個入口方法set_entry_point
- 根據(jù)平臺調(diào)用os::create_thread()方法來創(chuàng)建一個OSThread對象【見第三節(jié)】
3、根據(jù)平臺創(chuàng)建OSThread
在這一步中削祈,OSThread的創(chuàng)建是根據(jù)平臺來選擇翅溺,這里我是使用Linux部分的代碼來進(jìn)行研究的
OSThread是一個平臺相關(guān)線程,OSThread由JavaThread對象創(chuàng)建并進(jìn)行管理髓抑。
在OSThread創(chuàng)建的過程中咙崎,會通過pthread方法來創(chuàng)建一個真正意義上的底層級線程,
os_linux.cpp$os::create_thread:
這個方法傳入三個參數(shù):
第一個是我們之前在JavaThread構(gòu)造方法中創(chuàng)建的JavaThread對象吨拍;
第二個是一個ThreadType對象褪猛,只有兩種可能,一種是os::compiler_thread密末,另一種是os::java_thread
第三個是,線程棧的大小
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
assert(thread->osthread() == NULL, "caller responsible");
// 為OSThread對象分配內(nèi)存
OSThread* osthread = new OSThread(NULL, NULL);
//(注:)關(guān)于這個構(gòu)造方法跛璧,并沒有做出特殊的操作严里,僅是初始化一些屬性,詳見下方補(bǔ)充
if (osthread == NULL) {
return false;
}
// 設(shè)置正確的狀態(tài)
osthread->set_thread_type(thr_type);
// 初始狀態(tài)應(yīng)該是ALLOCATED 而不是INITIALIZED
osthread->set_state(ALLOCATED);
//使得JavaThread的osthread指針指向新建的osthread
thread->set_osthread(osthread);
// 初始化線程
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// stack size
if (os::Linux::supports_variable_stack_size()) {
// 如果調(diào)用者沒有指定stack size追城,那么就需要對這個stack size進(jìn)行計算
if (stack_size == 0) {
stack_size = os::Linux::default_stack_size(thr_type);
switch (thr_type) {
case os::java_thread:
// Java threads use ThreadStackSize which default value can be
// changed with the flag -Xss
assert (JavaThread::stack_size_at_create() > 0, "this should be set");
stack_size = JavaThread::stack_size_at_create();
break;
case os::compiler_thread:
if (CompilerThreadStackSize > 0) {
stack_size = (size_t)(CompilerThreadStackSize * K);
break;
} // else fall through:
// use VMThreadStackSize if CompilerThreadStackSize is not defined
case os::vm_thread:
case os::pgc_thread:
case os::cgc_thread:
case os::watcher_thread:
if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
break;
}
}
stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
pthread_attr_setstacksize(&attr, stack_size);
} else {
// let pthread_create() pick the default value.
}
// glibc guard page
pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
ThreadState state;
{
// Serialize thread creation if we are running with fixed stack LinuxThreads
// (好吧刹碾,我也不是很懂這句注釋的意思,
// 嘗試著直譯一下:如果我們正在運(yùn)行的是固定stack的LinuxThreads座柱,那么就序列化線程的創(chuàng)建過程)
bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
if (lock) {
os::Linux::createThread_lock()->lock_without_safepoint_check();
}
pthread_t tid;
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
//(注:)這個方法是c在Linux下創(chuàng)建并執(zhí)行一個平臺層的線程的方法
// 新建好的線程迷帜,應(yīng)該執(zhí)行的是java_start方法(即第二個參數(shù))
//如果返回值是0,表示線程創(chuàng)建成功色洞,否則表示線程創(chuàng)建失敗,關(guān)于這個方法的詳細(xì)介紹戏锹,見下方補(bǔ)充
pthread_attr_destroy(&attr);
// 下面是平臺級線程創(chuàng)建失敗的一些處理
// 如果osthread創(chuàng)建失敗,就把JAVAThread的osthread設(shè)置為NULL
if (ret != 0) {
if (PrintMiscellaneous && (Verbose || WizardMode)) {
perror("pthread_create()");
}
// Need to clean up stuff we've allocated so far
thread->set_osthread(NULL);
delete osthread;
if (lock) os::Linux::createThread_lock()->unlock();
return false;
}
// Store pthread info into the OSThread
// 這個tid是剛才新建的底層級線程的一個標(biāo)識符火诸,我們需要通過這個標(biāo)識符來管理底層級線程
osthread->set_pthread_id(tid);
// 在這里停頓锦针!直到子線程的初始化完成,或者子線程被放棄(考慮到線程中還有線程的情況)
{
Monitor* sync_with_child = osthread->startThread_lock();
MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
while ((state = osthread->get_state()) == ALLOCATED) {
sync_with_child->wait(Mutex::_no_safepoint_check_flag);
}
}
if (lock) {
os::Linux::createThread_lock()->unlock();
}
}
// Aborted due to thread limit being reached
if (state == ZOMBIE) {
thread->set_osthread(NULL);
delete osthread;
return false;
}
// The thread is returned suspended (in state INITIALIZED),
// and is started higher up in the call chain
assert(state == INITIALIZED, "race condition");
return true;
}
補(bǔ)充OSThread::OSThread:
OSThread::OSThread(OSThreadStartFunc start_proc, void* start_parm) {
pd_initialize();
//這個方法更是沒有什么好講的置蜀,只是將值初始化為初始值(及0或NULL)
//要是要看的話奈搜,這個方法在os/linux/vm/osThread_linux.cpp
set_start_proc(start_proc);
set_start_parm(start_parm);
set_interrupted(false);
}
補(bǔ)充創(chuàng)建系統(tǒng)級線程的pthread_create()方法
這個方法很長,做了以下的幾件事:
1盯荤、 創(chuàng)建一個osthread對象馋吗,并初始化這個對象,這個初始化包括:設(shè)置thread_type,設(shè)置線程狀態(tài)
2秋秤、讓我們傳進(jìn)來的JavaThread對象保存這個osthread對象的引用
3宏粤、中間還有一些設(shè)置線程棧數(shù)量脚翘,獲取鎖之類的操作
4、864行商架,int ret = pthread_create(&tid, &attr, (void* ()(void)) java_start, thread);這個方法是C++創(chuàng)建線程的庫方法堰怨,通過調(diào)用這個方法,會創(chuàng)建一個C++ 線程并使線程進(jìn)入就緒狀態(tài)蛇摸,即可以開始運(yùn)行
5备图、這個線程將執(zhí)行java_start這個方法【見第四節(jié)】
6、開始收尾赶袄,清理一下內(nèi)存
4揽涮、java_start方法
這個方法是OSThread創(chuàng)建的底層級線程中將要執(zhí)行的方法,它的描述是:為所有新建的線程啟動例程
// 線程為所有新創(chuàng)建的線程啟動例程饿肺。
static void *java_start(Thread *thread) {
// Try to randomize the cache line index of hot stack frames.
// This helps when threads of the same stack traces evict each other's
// cache lines. The threads can be either from the same JVM instance, or
// from different JVM instances. The benefit is especially true for
// processors with hyperthreading technology.
static int counter = 0;
int pid = os::current_process_id();
alloca(((pid ^ counter++) & 7) * 128);
ThreadLocalStorage::set_thread(thread);
OSThread* osthread = thread->osthread();
Monitor* sync = osthread->startThread_lock();
// non floating stack LinuxThreads needs extra check, see above
if (!_thread_safety_check(thread)) {
// notify parent thread
MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
osthread->set_state(ZOMBIE);
sync->notify_all();
return NULL;
}
// thread_id is kernel thread id (similar to Solaris LWP id)
osthread->set_thread_id(os::Linux::gettid());
if (UseNUMA) {
int lgrp_id = os::numa_get_group_id();
if (lgrp_id != -1) {
thread->set_lgrp_id(lgrp_id);
}
}
// initialize signal mask for this thread
os::Linux::hotspot_sigmask(thread);
// initialize floating point control register
os::Linux::init_thread_fpu_state();
// 與父進(jìn)程進(jìn)行新號交互(handshaking)
{
MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
// 通知父線程
osthread->set_state(INITIALIZED);
sync->notify_all();
// wait until os::start_thread()
while (osthread->get_state() == INITIALIZED) {
sync->wait(Mutex::_no_safepoint_check_flag);
}
}
// 調(diào)用一個更高級別的開始例程(見第五節(jié))
thread->run();
return 0;
}
這個方法做的事:
1蒋困、拿到這個線程的線程id
2、分配內(nèi)存敬辣?
3雪标、設(shè)置ThreadLocal
4、線程安全檢測
5溉跃、中間還有一些東西村刨,不想關(guān)心
6、同樣地撰茎,再次設(shè)置這個thread的osthrad的狀態(tài)
7嵌牺、調(diào)用Thread的run方法,但是這個run方法是個虛方法龄糊,實(shí)際上調(diào)用的是JavaThread的run方法【見第五節(jié)】
5逆粹、JavaThread::run()
// Java Thread第一個調(diào)用的例程
void JavaThread::run() {
//初始化相關(guān)字段
// initialize thread-local alloc buffer related fields
this->initialize_tlab();
// used to test validitity of stack trace backs
this->record_base_of_stack_pointer();
// Record real stack base and size.
this->record_stack_base_and_size();
// Initialize thread local storage; set before calling MutexLocker
this->initialize_thread_local_storage();
this->create_stack_guard_pages();
this->cache_global_variables();
//線程基本初始化完成,在虛擬機(jī)中可以當(dāng)作是safepoint代碼來處理了
//于是將線程的狀態(tài)從 _thread_new切換到_thread_in_vm
// Thread is now sufficient initialized to be handled by the safepoint code as being
// in the VM. Change thread state from _thread_new to _thread_in_vm
ThreadStateTransition::transition_and_fence(this, _thread_new, _thread_in_vm);
assert(JavaThread::current() == this, "sanity check");
assert(!Thread::current()->owns_locks(), "sanity check");
DTRACE_THREAD_PROBE(start, this);
// This operation might block. We call that after all safepoint checks for a new thread has
// been completed.
//這個操作有可能會被阻塞炫惩,我們需要要在所有safepoint檢查完這個新線程以后才能調(diào)用此方法
this->set_active_handles(JNIHandleBlock::allocate_block());
if (JvmtiExport::should_post_thread_life()) {
JvmtiExport::post_thread_start(this);
}
EventThreadStart event;
if (event.should_commit()) {
event.set_javalangthread(java_lang_Thread::thread_id(this->threadObj()));
event.commit();
}
// We call another function to do the rest so we are sure that the stack addresses used
// from there will be lower than the stack base just computed
thread_main_inner();
// 注意僻弹,一旦運(yùn)行到這里,線程就不再合法(valid)了
}
這個方法完成的事有:
1他嚷、初始化相關(guān)字段
2奢方、將線程的狀態(tài)從 _thread_new切換到_thread_in_vm
3、調(diào)用thread_main_inner()方法【見第六節(jié)】
(這里說的safepoint應(yīng)該就是GC中要用到的safepoint吧爸舒,但是all safepoint checks for a new thread是什么意思蟋字,safepoint還能檢查線程..?暫且擱置)
6扭勉、JavaThread::thread_main_inner()
void JavaThread::thread_main_inner() {
assert(JavaThread::current() == this, "sanity check");
assert(this->threadObj() != NULL, "just checking");
// Execute thread entry point unless this thread has a pending exception
// or has been stopped before starting.
// Note: Due to JVM_StopThread we can have pending exceptions already!
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;
}
然后又回到了之前的我們設(shè)置的entry_point():(可回看第一節(jié))
thread_entry方法的補(bǔ)充:
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(),`
THREAD);
}
注意這個vmSymbols::run_method_name(),非常關(guān)鍵鹊奖!
這個方法是在vmSymbolHandles中用宏來定義的,而這個vmSymbolHandles的描述是:
// The class vmSymbols is a name space for fast lookup of
// symbols commonly used in the VM.
//
// Sample usage:
//
// Symbol* obj = vmSymbols::java_lang_Object();
大意翻譯為:vmSymbols是一個VM中使用的涂炎,用于常用符號的快速查找的命名空間
于是忠聚,我們在這個命名空間找到了這樣的一行代碼:
template(run_method_name, "run")
也就是說设哗,這里調(diào)用的是java.lang.Thread對象的run()方法!
總結(jié)
撇開源碼两蟀,整個過程大概是:
- 在Java中网梢,使用java.lang.Thread的構(gòu)造方法來構(gòu)建一個java.lang.Thread對象,此時只是對這個對象的部分字段(例如線程名赂毯,優(yōu)先級等)進(jìn)行初始化战虏;
- 調(diào)用java.lang.Thread對象的start()方法,開始此線程党涕。此時烦感,在start()方法內(nèi)部,調(diào)用start0() 本地方法來開始此線程膛堤;
- start0()在VM中對應(yīng)的是JVM_StartThread手趣,也就是,在VM中肥荔,實(shí)際運(yùn)行的是JVM_StartThread方法(宏),在這個方法中绿渣,創(chuàng)建了一個JavaThread對象;
- 在JavaThread對象的創(chuàng)建過程中燕耿,會根據(jù)運(yùn)行平臺創(chuàng)建一個對應(yīng)的OSThread對象中符,且JavaThread保持這個OSThread對象的引用;
- 在OSThread對象的創(chuàng)建過程中缸棵,創(chuàng)建一個平臺相關(guān)的底層級線程舟茶,如果這個底層級線程失敗谭期,那么就拋出異常堵第;
- 在正常情況下,這個底層級的線程開始運(yùn)行隧出,并執(zhí)行java.lang.Thread對象的run方法踏志;
- 當(dāng)java.lang.Thread生成的Object的run()方法執(zhí)行完畢返回后,或者拋出異常終止后胀瞪,終止native thread;
- 最后就是釋放相關(guān)的資源(包括內(nèi)存针余、鎖等)
在上述過程,穿插著各種的判斷檢測凄诞,其中很大一部分都是關(guān)于各種層次下的線程的狀態(tài)的檢測圆雁,在JVM中,無論哪種層次的線程帆谍,都只允許執(zhí)行一次伪朽。
最后
本文的參考資料比較多,但是由于我在寫作的時候沒有進(jìn)行做引用記錄汛蝙,于是就不一一列舉了烈涮。但是,所有的參考引用,我在文章的引用位置都以超鏈接的方式放出來了煤惩。
寫作相當(dāng)辛苦笛质,所以能不能不要抄襲/剽竊我的勞動成果,謝謝讶舰。
若需轉(zhuǎn)載授權(quán)鞍盗,在評論留言即可。
未經(jīng)授權(quán)绘雁,不得轉(zhuǎn)載橡疼。