- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- OpenJDK源碼解析之System.out.println詳解
可能不少小伙伴習慣在代碼中使用sout打印一些信息,就像這樣:
System.out.println("hello world!")
做為一位資深干碼人,本著(zhù)弘揚黨求真務(wù)實(shí)的精神,必須得來(lái)看看這個(gè)sout有何玄機~~
首先看調用就知道,out是System類(lèi)的一個(gè)公共靜態(tài)成員變量,進(jìn)入System.java中:
public final static PrintStream out = null;
嗯,不止是public,還是final的。不管,來(lái)找找out是在哪里賦值的。。。。。。日嘛找半天沒(méi)找到?那就試試直接在類(lèi)中搜索: out = ,結果如下:
完犢子,整個(gè)System類(lèi)一共將近1300行的代碼,只找到一個(gè)和out賦值相關(guān)的,還是啥子局部變量fdOut,看起來(lái)和out屬性沒(méi)有什么關(guān)聯(lián)啊~ 往上滑滑看看這個(gè)是什么方法:
private static void initializeSystemClass() { ...... FileInputStream fdIn = new FileInputStream(FileDescriptor.in); FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); setIn0(new BufferedInputStream(fdIn)); setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding"))); ...... }
原來(lái)如此,看到initializeSystemClass方法,似乎一切都明了了~~
initializeSystemClass不是給我們調用的,這個(gè)方法會(huì )在vm線(xiàn)程初始化后被虛擬機調用。其定義在thread.cpp中:
static void call_initializeSystemClass(TRAPS) { Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_System(), true, CHECK); instanceKlassHandle klass (THREAD, k); JavaValue result(T_VOID); JavaCalls::call_static(&result, klass, vmSymbols::initializeSystemClass_name(), vmSymbols::void_method_signature(), CHECK); }
首先獲取Klass(類(lèi)元信息在虛擬機中的結構表示,就是一個(gè)c++中的類(lèi)),在vmSymbols.hpp中找找java_lang_System對應的值:
template(java_lang_System, "java/lang/System")
可以看到代表的就是java.lang.System類(lèi),同時(shí),initializeSystemClass_name也定義在vmSymbols.hpp中:
template(initializeSystemClass_name, "initializeSystemClass")
這里使用JavaCalls::call_static就是調用java.lang.System的靜態(tài)方法initializeSystemClass。還要再啰嗦一句,call_initializeSystemClass是在何處調用的?我們回到thread.cpp中,進(jìn)入Threads::create_vm方法,在其中找到了call_initializeSystemClass方法的調用:
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { ...... call_initializeSystemClass(CHECK_0); ...... }
然后看看Threads::create_vm是在何處調用的,我們進(jìn)入jni.cpp,找到JNI_CreateJavaVM方法:
_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) { ...... result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again); if (result == JNI_OK) { ...... } else { ...... } ...... }
現在已經(jīng)知道了,JVM啟動(dòng)后會(huì )在初始化JVM的時(shí)候調用CreateJavaVM,進(jìn)而調用initializeSystemClass方法。但是out屬性是如何設置的呢?我們回到j(luò )ava.lang.System#initializeSystemClass方法:
private static void initializeSystemClass() { ...... FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); ...... } private static PrintStream newPrintStream(FileOutputStream fos, String enc) { if (enc != null) { try { return new PrintStream(new BufferedOutputStream(fos, 128), true, enc); } catch (UnsupportedEncodingException uee) {} } return new PrintStream(new BufferedOutputStream(fos, 128), true); }
這個(gè)方法中唯一和out相關(guān)的就是這個(gè)setOut0方法調用了,我們來(lái)看看這個(gè)方法:
private static native void setOut0(PrintStream out);
嗯,這個(gè)是一個(gè)native方法,沒(méi)辦法,又只有回到JVM源碼了。怎么找呢?首選組裝一下本地方法名,根據規則setOut0對應的本地方法應該叫:Java_java_lang_System_setOut0,我們到源碼中找找,然后在System.c中找到了該方法
JNIEXPORT void JNICALL Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream) { jfieldID fid = (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;"); if (fid == 0) return; (*env)->SetStaticObjectField(env,cla,fid,stream); }
首先獲取了java.io.PrintStread類(lèi)型的out靜態(tài)成員,嗯,這個(gè)就是我們java.lang.System類(lèi)的靜態(tài)成員out:
public final static PrintStream out;
然后將stream參數賦值給它,這個(gè)stream就是initializeSystemClass方法中通過(guò)newPrintStream方法創(chuàng )建的PrintStream 對象。到現在我們已經(jīng)明白了,out原來(lái)是這樣賦值的,真麻煩~
趁熱打鐵,弄明白了out,接下來(lái)看看println。既然out是PrintStream對象,那么到PrintStream中看看println方法:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
Java代碼看起來(lái)是要親切多了,但是這個(gè)synchronized是個(gè)什么鬼???
前面我們發(fā)現println方法竟然有個(gè)synchronized關(guān)鍵字,經(jīng)常在項目中使用sout的小伙伴會(huì )不會(huì )感覺(jué)腦袋嗡嗡的?為什么要嗡嗡?不要忘記我們前面通過(guò)JVM源碼跟蹤的System.out的賦值過(guò)程,這個(gè)out可是單例!你個(gè)加了synchronized(this)的虛方法,還是單例的,如果在系統中進(jìn)行并發(fā)使用,后果不用我多說(shuō)吧?
那可能有人就要說(shuō)了,那我不用println(“hello world!”)了嘛,我換成print總行了吧?
System.out.print("hello world!"); System.out.print("\n");
因為print方法好像沒(méi)有加鎖?。?/p>
public void print(String s) { if (s == null) { s = "null"; } write(s); }
那我們看看write方法,不好意思:
private void write(String s) { try { synchronized (this) { ensureOpen(); textOut.write(s); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush && (s.indexOf('\n') >= 0)) out.flush(); } } ...... }
不知道有沒(méi)有小伙伴經(jīng)常在項目中使用System.out.println來(lái)輸出"日志"?千萬(wàn)不要亂用喲,不然說(shuō)不定哪天就被out了~~~
到此這篇關(guān)于OpenJDK源碼解析之System.out.println詳解的文章就介紹到這了,更多相關(guān)OpenJDK源碼解析內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站