[java] System類深度解析
概述
大部分寫的第一個java功能,應該都是如下這行程式碼:
System.out.println("hello world");
這裡即用到了System類,本篇文章就將分析System類的實現原理。在java裡面System類是一個final修飾的類,意味著它的方法和屬性都不能被修改。
System 類包含了多個有用的欄位和方法,它不能被例項化。在system類提供的功能當中,有標準輸入、標準輸出和錯誤輸出流,訪問外部定義的屬性和環境變數;載入檔案和庫的方法;以及用於快速複製陣列的一部分的實用方法。
構造方法如下:
/** Don't let anyone instantiate this class */ private System() { }
註釋裡寫的非常清楚:不讓任何人例項化這個類。
關於System.out.println()實現原理
System是一個類,而out則是該類的一個成員變數,定義如下:
/** * The "standard" output stream. This stream is already * open and ready to accept output data. Typically this stream * corresponds to display output or another output destination * specified by the host environment or user. * <p> * For simple stand-alone Java applications, a typical way to write * a line of output data is: * <blockquote><pre> * System.out.println(data) * </pre></blockquote> * <p> * See the <code>println</code> methods in class <code>PrintStream</code>. * * @see java.io.PrintStream#println() * @see java.io.PrintStream#println(boolean) * @see java.io.PrintStream#println(char) * @see java.io.PrintStream#println(char[]) * @see java.io.PrintStream#println(double) * @see java.io.PrintStream#println(float) * @see java.io.PrintStream#println(int) * @see java.io.PrintStream#println(long) * @see java.io.PrintStream#println(java.lang.Object) * @see java.io.PrintStream#println(java.lang.String) */ public final static PrintStream out = null;
可以看到,out實際上是類PrintSteam的一個例項, 而println()就是PrintStream類的一個方法而已。PrintStream類是“標準”輸出流。此流已經開啟並準備接受輸出資料。通常,該流對應於主機環境或使用者指定的顯示輸出或另一輸出目的地。而在我們的編輯器裡面,它就是輸出在console上的。
但是這裡的out變數是null並沒有例項化,那麼它是在哪裡例項化的呢?答案在System類的initializeSystemClass方法裡:
/** * Initialize the system class. Called after thread initialization. */ private static void initializeSystemClass() { ... props = new Properties(); initProperties(props); // initialized by the VM ... sun.misc.VM.saveAndRemoveProperties(props); lineSeparator = props.getProperty("line.separator"); sun.misc.Version.init(); 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"))); // Load the zip library now in order to keep java.util.zip.ZipFile // from trying to use itself to load this library later. loadLibrary("zip"); // Setup Java signal handlers for HUP, TERM, and INT (where available). Terminator.setup(); ... sun.misc.VM.initializeOSEnvironment(); // The main thread is not added to its thread group in the same // way as other threads; we must do it ourselves here. Thread current = Thread.currentThread(); current.getThreadGroup().add(current); // register shared secrets setJavaLangAccess(); ... sun.misc.VM.booted(); }
在上面的程式碼當中,有一行呼叫了setOut0()方法,該方法就是對out屬性初始化的方法。不出意外,它又是一個native方法。在open jdk當中,有它的實現程式碼,如下:
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);
}
setOut0(PrintStream ps)的作用,就是將PrintStream設定為System類的out靜態變數。
而在PrintSteam類裡面,println方法有多個過載的實現,它們最終都是呼叫的一個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();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
該write方法呼叫的是PrintSteam類的成員變數BufferedWriter類的一個例項,在BufferedWriter類裡面,我們可以看到write方法的實現:
/**
* Writes a portion of an array of characters.
*
* <p> Ordinarily this method stores characters from the given array into
* this stream's buffer, flushing the buffer to the underlying stream as
* needed. If the requested length is at least as large as the buffer,
* however, then this method will flush the buffer and write the characters
* directly to the underlying stream. Thus redundant
* <code>BufferedWriter</code>s will not copy data unnecessarily.
*
* @param cbuf A character array
* @param off Offset from which to start reading characters
* @param len Number of characters to write
*
* @exception IOException If an I/O error occurs
*/
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if (len >= nChars) {
/* If the request length exceeds the size of the output buffer,
flush the buffer and then write the data directly. In this
way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(cbuf, off, len);
return;
}
int b = off, t = off + len;
while (b < t) {
int d = min(nChars - nextChar, t - b);
System.arraycopy(cbuf, b, cb, nextChar, d);
b += d;
nextChar += d;
if (nextChar >= nChars)
flushBuffer();
}
}
}
因此,例項化了PrintStream物件,然後藉助它去呼叫println方法,這就是大致的思想。
System.gc方法
/**
* Runs the garbage collector.
* <p>
* Calling the <code>gc</code> method suggests that the Java Virtual
* Machine expend effort toward recycling unused objects in order to
* make the memory they currently occupy available for quick reuse.
* When control returns from the method call, the Java Virtual
* Machine has made a best effort to reclaim space from all discarded
* objects.
* <p>
* The call <code>System.gc()</code> is effectively equivalent to the
* call:
* <blockquote><pre>
* Runtime.getRuntime().gc()
* </pre></blockquote>
*
* @see java.lang.Runtime#gc()
*/
public static void gc() {
Runtime.getRuntime().gc();
}
程式碼如上,又是一個native方法,結合註釋即可理解它的含義。另外值得注意的是,這個方法並不是強制讓虛擬機器回收不用的物件,這個方法只是提醒虛擬機器,程式設計師希望你在這回收一下物件,但回不回收還是虛擬機器來決定,也就是說程式設計師對回不回收沒有絕對的控制權。關於這個問題在我的另一篇部落格裡也有提到。
System.exit方法
定義如下:
/**
* Terminates the currently running Java Virtual Machine. The
* argument serves as a status code; by convention, a nonzero status
* code indicates abnormal termination.
* <p>
* This method calls the <code>exit</code> method in class
* <code>Runtime</code>. This method never returns normally.
* <p>
* The call <code>System.exit(n)</code> is effectively equivalent to
* the call:
* <blockquote><pre>
* Runtime.getRuntime().exit(n)
* </pre></blockquote>
*
* @param status exit status.
* @throws SecurityException
* if a security manager exists and its <code>checkExit</code>
* method doesn't allow exit with the specified status.
* @see java.lang.Runtime#exit(int)
*/
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
這個方法的作用是結束當前正在執行的Java虛擬機器,int型別的status表示退出的狀態碼,非零表示異常終止。注意:無論status為何值程式都會退出,和return 相比有不同的是:return是回到上一層,而System.exit(status)是回到最上層。