OpenJDK源碼解析之System.out.println詳解
一、前戲
可能不少小伙伴習慣在代碼中使用sout打印一些信息,就像這樣:
System.out.println("hello world!")
做為一位資深干碼人,本著弘揚黨求真務實的精神,必須得來看看這個sout有何玄機~~
首先看調用就知道,out是System類的一個公共靜態(tài)成員變量,進入System.java中:
public final static PrintStream out = null;
嗯,不止是public,還是final的。不管,來找找out是在哪里賦值的。。。。。。日嘛找半天沒找到?那就試試直接在類中搜索: out = ,結果如下:

完犢子,整個System類一共將近1300行的代碼,只找到一個和out賦值相關的,還是啥子局部變量fdOut,看起來和out屬性沒有什么關聯(lián)啊~ 往上滑滑看看這個是什么方法:
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")));
......
}
原來如此,看到initializeSystemClass方法,似乎一切都明了了~~
二、JVM源碼分析
initializeSystemClass不是給我們調用的,這個方法會在vm線程初始化后被虛擬機調用。其定義在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(類元信息在虛擬機中的結構表示,就是一個c++中的類),在vmSymbols.hpp中找找java_lang_System對應的值:
template(java_lang_System, "java/lang/System")
可以看到代表的就是java.lang.System類,同時,initializeSystemClass_name也定義在vmSymbols.hpp中:
template(initializeSystemClass_name, "initializeSystemClass")
這里使用JavaCalls::call_static就是調用java.lang.System的靜態(tài)方法initializeSystemClass。還要再啰嗦一句,call_initializeSystemClass是在何處調用的?我們回到thread.cpp中,進入Threads::create_vm方法,在其中找到了call_initializeSystemClass方法的調用:
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
......
call_initializeSystemClass(CHECK_0);
......
}
然后看看Threads::create_vm是在何處調用的,我們進入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 {
......
}
......
}
現(xiàn)在已經(jīng)知道了,JVM啟動后會在初始化JVM的時候調用CreateJavaVM,進而調用initializeSystemClass方法。但是out屬性是如何設置的呢?我們回到java.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);
}
這個方法中唯一和out相關的就是這個setOut0方法調用了,我們來看看這個方法:
private static native void setOut0(PrintStream out);
嗯,這個是一個native方法,沒辦法,又只有回到JVM源碼了。怎么找呢?首選組裝一下本地方法名,根據(jù)規(guī)則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類型的out靜態(tài)成員,嗯,這個就是我們java.lang.System類的靜態(tài)成員out:
public final static PrintStream out;
然后將stream參數(shù)賦值給它,這個stream就是initializeSystemClass方法中通過newPrintStream方法創(chuàng)建的PrintStream 對象。到現(xiàn)在我們已經(jīng)明白了,out原來是這樣賦值的,真麻煩~
趁熱打鐵,弄明白了out,接下來看看println。既然out是PrintStream對象,那么到PrintStream中看看println方法:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
Java代碼看起來是要親切多了,但是這個synchronized是個什么鬼???
三、坑?
前面我們發(fā)現(xiàn)println方法竟然有個synchronized關鍵字,經(jīng)常在項目中使用sout的小伙伴會不會感覺腦袋嗡嗡的?為什么要嗡嗡?不要忘記我們前面通過JVM源碼跟蹤的System.out的賦值過程,這個out可是單例!你個加了synchronized(this)的虛方法,還是單例的,如果在系統(tǒng)中進行并發(fā)使用,后果不用我多說吧?
那可能有人就要說了,那我不用println(“hello world!”)了嘛,我換成print總行了吧?
System.out.print("hello world!");
System.out.print("\n");
因為print方法好像沒有加鎖?。?/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();
}
}
......
}
四、總結
不知道有沒有小伙伴經(jīng)常在項目中使用System.out.println來輸出"日志"?千萬不要亂用喲,不然說不定哪天就被out了~~~
到此這篇關于OpenJDK源碼解析之System.out.println詳解的文章就介紹到這了,更多相關OpenJDK源碼解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Cloud Eureka服務治理的實現(xiàn)
服務治理是微服務框架中最為核心和基礎的模塊,它主要是用來實現(xiàn)各個微服務實例的自動化注冊與發(fā)現(xiàn)。這篇文章主要介紹了Spring Cloud Eureka服務治理的實現(xiàn),感興趣的小伙伴們可以參考一下2018-06-06
Java 客戶端向服務端上傳mp3文件數(shù)據(jù)的實例代碼
這篇文章主要介紹了Java 客戶端向服務端上傳mp3文件數(shù)據(jù)的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09
maven無法依賴spring-cloud-stater-zipkin的解決方案
這篇文章主要介紹了maven無法依賴spring-cloud-stater-zipkin如何解決,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05
rabbitmq的消息持久化處理開啟,再關閉后,消費者啟動報錯問題
這篇文章主要介紹了rabbitmq的消息持久化處理開啟,再關閉后,消費者啟動報錯問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
SpringBoot整合Swagger Api自動生成文檔的實現(xiàn)
本文主要介紹了SpringBoot整合Swagger Api自動生成文檔的實,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06

