概述
大部分写的第一个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)是回到最上层。