初識(shí)System類(lèi)
System類(lèi)是從JDK1.0就被定義了的一個(gè)類(lèi),本篇所有解讀都是基于OpenJdk8而來(lái)。System類(lèi)由final修飾符修飾,所以它不能被繼承擴(kuò)展,而System類(lèi)只有一個(gè)私有的無(wú)參構(gòu)造函數(shù),所以它不能被實(shí)例化,我們只能使用這個(gè)類(lèi)里的靜態(tài)方法。
public final class System {
//此處省略代碼XX行
/* Don’t let anyone instantiate this class /
private System() {
}
//此處省略代碼XX行
}
System類(lèi)為我們提供了一些方便的工具,通過(guò)它我們可以很方便的做以下幾個(gè)事兒:
- 使用System類(lèi)使用標(biāo)準(zhǔn)輸入流、標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤輸出流
- 使用System類(lèi)訪(fǎng)問(wèn)外部定義的系統(tǒng)屬性和環(huán)境變量
- 通過(guò)System類(lèi)動(dòng)態(tài)加載動(dòng)態(tài)鏈接庫(kù)
- 使用System類(lèi)快速的復(fù)制部分?jǐn)?shù)組元素
System類(lèi)初始化
System類(lèi)初始化可以分為兩部分:本地方法初始化、VM調(diào)用初始化
什么是本地方法?
一個(gè)本地方法就是由Java調(diào)用的非Java語(yǔ)言實(shí)現(xiàn)的接口,并保存在動(dòng)態(tài)鏈接庫(kù)中
為什么要用本地方法?
首先,有些層次的任務(wù)用Java實(shí)現(xiàn)起來(lái)比較不易,或者當(dāng)我們對(duì)程序執(zhí)行的效率比較在意的時(shí)候,使用本地方法可能是更好的選擇,如:與非Java環(huán)境進(jìn)行交互、與底層系統(tǒng)或硬件交換信息
其次,JVM是一個(gè)解釋器,它的一部分實(shí)現(xiàn)就是使用的C語(yǔ)言,它的運(yùn)行也需要操作系統(tǒng)的支持,與操作系統(tǒng)的交互就是通過(guò)本地方法
System類(lèi)本地方法初始化
System類(lèi)中定義了許多本地方法,Java代碼要調(diào)用一個(gè)本地方法需要2個(gè)步驟:
1、 通過(guò)System.loadLibrary()方法將包含本地方法的動(dòng)態(tài)鏈接庫(kù)加載進(jìn)入內(nèi)存中,如在Win32環(huán)境下執(zhí)行System.loadLibrary("foo")就會(huì)把foo.dll加載進(jìn)內(nèi)存中
2、 當(dāng)Java要調(diào)用本地方法時(shí),JVM會(huì)在已加載進(jìn)內(nèi)存的動(dòng)態(tài)鏈接庫(kù)中定位并鏈接要調(diào)用的本地方法,然后才可以執(zhí)行本地方法。比如要執(zhí)行Foo.g()方法,JVM會(huì)在foo.dll動(dòng)態(tài)鏈接庫(kù)中定位并鏈接Java_Foo_g這個(gè)本地方法
顯然步驟2有些低效,Java要調(diào)用本地方法是如果能省去定位鏈接豈不是更好嗎?registerNatives()方法就是為了代替步驟2應(yīng)運(yùn)而生,在許多類(lèi)里都能發(fā)現(xiàn)它的蹤影,作用都是一樣的,比如:Object類(lèi)、Thread類(lèi)等等。
/* register the natives via the static initializer.
*
* VM will invoke the initializeSystemClass method to complete
* the initialization for this class separated from clinit.
* Note that to use properties set by the VM, see the constraints
* described in the initializeSystemClass method.
*/
private static native void registerNatives();
static {
registerNatives();
}
registerNatives()方法在靜態(tài)代碼塊中被調(diào)用,編譯器會(huì)將靜態(tài)字段和靜態(tài)代碼塊重新整合進(jìn)<clinit>()方法中,當(dāng)類(lèi)被加載時(shí)<clinit>()方法被調(diào)用,靜態(tài)代碼塊得以執(zhí)行。
registerNatives()方法讓程序可以主動(dòng)的去鏈接本地方法,這樣JVM就不用去動(dòng)態(tài)鏈接庫(kù)中查找定位本地方法了,Java可以直接調(diào)用本地方法執(zhí)行。
我們可以到Jdk源碼里翻一下它的實(shí)現(xiàn)(不知道怎么找的看官別急,且聽(tīng)下回分解)
/* Only register the performance-critical methods */
static JNINativeMethod methods[] = {
//這里主動(dòng)注冊(cè)鏈接了三個(gè)方法,分別對(duì)應(yīng)著System類(lèi)里的System.currentTimeMillis()、System.nanoTime()和System.arrayCopy()三個(gè)方法
{"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis},
{"nanoTime", "()J", (void *)&JVM_NanoTime},
{"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};
#undef OBJ
JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
//主動(dòng)注冊(cè)鏈接操作就是在這里干的
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
System類(lèi)VM初始化
System類(lèi)中初始化主要通過(guò)initializeSystemClass()方法,關(guān)于initializeSystemClass方法的調(diào)用,javadoc原文有兩段是這么說(shuō)的
VM will invoke the initializeSystemClass method to complete
the initialization for this class separated from clinit.
Initialize the system class, Called after thread initialization.
也就是說(shuō)initializeSystemClass方法是由VM來(lái)調(diào)用的,而且是在線(xiàn)程被初始化以后就調(diào)用了,我大概翻了一下hotspot JVM的實(shí)現(xiàn),找到了這個(gè)方法被調(diào)用的地方
//這個(gè)方法負(fù)責(zé)調(diào)用System類(lèi)的initializeSystemClass方法
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);
}
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
//此處省略代碼XX行
{
TraceTime timer("Initialize java.lang classes", TraceStartupTime);
if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}
initialize_class(vmSymbols::java_lang_String(), CHECK_0); //這里加載了java.lang.String類(lèi)
initialize_class(vmSymbols::java_lang_System(), CHECK_0); //這里加載了java.lang.System類(lèi),然而還沒(méi)有調(diào)用System類(lèi)的initializeSystemClass方法
initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0); //這里加載了java.lang.ThreadGroup類(lèi)
Handle thread_group = create_initial_thread_group(CHECK_0);
Universe::set_main_thread_group(thread_group());
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0); //這里加載了java.lang.Thread類(lèi)
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
java_lang_Thread::set_thread_status(thread_object,
java_lang_Thread::RUNNABLE);
initialize_class(vmSymbols::java_lang_Class(), CHECK_0); //這里加載了java.lang.Class類(lèi)
initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0); //這里加載了java.lang.reflect.Method類(lèi)
initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0); //這里加載了java.lang.ref.Finalizer類(lèi)
call_initializeSystemClass(CHECK_0); //這是調(diào)用了System類(lèi)的initializeSystemClass方法
JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));
initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
}
//此處省略代碼XX行
}
源碼中調(diào)用System類(lèi)的initializeSystemClass方法是在create_vm方法中,也就是JVM創(chuàng)建時(shí),在加載了String、System、ThreadGroup、Thread、Class、Method、Finalizer這幾個(gè)類(lèi)之后,才調(diào)用了System類(lèi)的initializeSystemClass方法,之后又加載了一些異常類(lèi)。
initializeSystemClass方法完成了System類(lèi)后續(xù)的初始化工作,主要有以下幾個(gè)部分:
1)初始化外部定義的系統(tǒng)屬性和環(huán)境變量
props = new Properties();
initProperties(props); // initialized by the VM
sun.misc.VM.saveAndRemoveProperties(props);
lineSeparator = props.getProperty("line.separator");
sun.misc.Version.init();
這些系統(tǒng)屬性和環(huán)境變量一部分是通過(guò)本地方法設(shè)置的,另一部分是通過(guò)System.setProperty()方法設(shè)置的,initProperties()方法就是一個(gè)本地方法,它的實(shí)現(xiàn)在Jdk源碼中的/src/share/native/java/lang/System.c文件中的Java_java_lang_System_initProperties方法。
初始化完成后我們將至少能通過(guò)System.getProperty()方法訪(fǎng)問(wèn)到以下屬性:(這里以O(shè)penJdk11的jshell環(huán)境舉例)
| 歡迎使用 JShell -- 版本 11.0.4
| 要大致了解該版本, 請(qǐng)鍵入: /help intro
jshell> System.getProperty("java.version"); //Java版本號(hào)
$1 ==> "11.0.4"
jshell> System.getProperty("java.runtime.version"); //Java運(yùn)行時(shí)版本號(hào)
$2 ==> "11.0.4+11-post-Ubuntu-1ubuntu218.04.3"
jshell> System.getProperty("java.runtime.name"); //Java運(yùn)行時(shí)名稱(chēng)
$3 ==> "OpenJDK Runtime Environment"
jshell> System.getProperty("java.vendor"); //Java供應(yīng)商
$4 ==> "Ubuntu"
jshell> System.getProperty("java.vendor.url"); //Java供應(yīng)商主頁(yè)
$5 ==> "https://ubuntu.com/"
jshell> System.getProperty("java.home"); //JDK安裝目錄
$6 ==> "/usr/lib/jvm/java-11-openjdk-amd64"
jshell> System.getProperty("java.class.version"); //Java Class版本號(hào),55.0應(yīng)該是對(duì)應(yīng)著Jdk11
$7 ==> "55.0"
jshell> System.getProperty("java.class.path"); //環(huán)境變量類(lèi)路徑, ClassLoader加載類(lèi)時(shí)使用
$8 ==> "."
jshell> System.getProperty("os.name"); //操作系統(tǒng)名稱(chēng)
$9 ==> "Linux"
jshell> System.getProperty("os.arch"); //操作系統(tǒng)架構(gòu)
$10 ==> "amd64"
jshell> System.getProperty("file.separator"); //文件分隔符
$12 ==> "/"
jshell> System.getProperty("path.separator"); //路徑分隔符
$13 ==> ":"
jshell> System.getProperty("line.separator"); //行分隔符
$14 ==> "\n"
jshell> System.getProperty("user.name"); //當(dāng)前用戶(hù)名
$15 ==> "mayun"
jshell> System.getProperty("user.home"); //當(dāng)前用戶(hù)home目錄
$16 ==> "/home/mayun"
jshell> System.getProperty("user.dir"); //當(dāng)前用戶(hù)工作目錄,默認(rèn)文件保存路徑
$17 ==> "/home/mayun"
2)初始化標(biāo)準(zhǔn)輸入、輸出流
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()方法是在加載完System類(lèi)以后就由JVM調(diào)用了,所以當(dāng)我們使用System類(lèi)時(shí),System類(lèi)的in、out、err三個(gè)字段已經(jīng)被初始化完成了。setIn0、setOut0和setErr0三個(gè)方法也是本地方法,實(shí)現(xiàn)在Jdk源碼的/src/share/native/java/lang/System.c文件中,對(duì)應(yīng)著Java_java_lang_System_setIn0、Java_java_lang_System_setOut0和Java_java_lang_System_setErr0三個(gè)方法。
歡迎關(guān)注我的公眾號(hào):米爸筆記,獲得獨(dú)家整理的學(xué)習(xí)資源和日常干貨推送