查看所有目录
说起System类,对我们来说既熟悉又陌生,熟悉的是我们刚接触java时就会用到这个类,甚至每天写代码也会用到这个类(System.out.println());陌生的是我们对这个类一知半解,它是如何实现的,为什么这样写就能在屏幕上输出东西。今天就让我们通过阅读它的源码揭开它那神秘的面纱。
基础知识:
深拷贝与浅拷贝:
浅拷贝:只是简单的对对象的引用进行复制。
深拷贝:拷贝对象的值和对象的内容。
类定义:
public final class System {}
System类是被final修饰的,不能被继承。
成员变量:
//标准输入流,一般接收键盘输入
public final static InputStream in = null;
//标准输出流,向控制台输出正确信息
public final static PrintStream out = null
//标准输出流,向控制台输出错误信息
public final static PrintStream err = null;
//安全管理器,用volatile关键字修饰,表示对象在多个线程间可见
private static volatile SecurityManager security = null;
//JAVA控制台对象与JAVA虚拟机相联,接收控制台输入,或向控制台输出信息。只能在控制台运行代码,否则获得对象为null。
private static volatile Console cons = null;
//用于承装系统属性,如os.name os.version等
private static Properties props;
//换行符widows返回"\r\n",UNIX系统返回"\n"
private static String lineSeparator;
看到这些成员变量,有些是公有的,有些是私有的。这些对象被赋值为null,而且有的被final修饰不能被重新赋值。找遍整个类也没看到在哪里初始化这些对象。但我们使用System.out.println();的时候并没有报错啊。能够使用,就说明对象在谋个时刻被创建了,那到底这些对象是什么时候被创建的呢。怀着无比好奇的心理,在类中查找,忽然我看到了如下代码。
static {
registerNatives();
private static native void registerNatives();
静态代码块,类被加载的时候执行,会不会这些对象是在registerNatives方法中创建的呢。再一看这个方法是本地方法。看下注释,也看不懂,但隐约能看到VM啊,initializer啊,initializeSystemClass method之类的,找了一下,发现类中有一个initializeSystemClass方法,大概能猜到,是VM会执行这个方法。
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")));
loadLibrary("zip");
Terminator.setup();
sun.misc.VM.initializeOSEnvironment();
Thread current = Thread.currentThread();
current.getThreadGroup().add(current);
setJavaLangAccess();
sun.misc.VM.booted();
}
在这个方法中可以看到刚才那些对象都是在这里被创建的。
lineSeparator = props.getProperty("line.separator");从系统中获取换行符。
setIn0(new BufferedInputStream(fdIn));设置标准输入流,也就是创建in对象,追溯到源头也就是创建了FileDescriptor.in对象,这个对象用于操作标准输入流,再往深了就到了虚拟机内部实现细节了,这里就有展开了,有兴趣的同学可以自行研究。
其它的对象像out、err创建过程同in类似。到这里我们终于明白了为什么执行System.out.println()时会向控制台输出东西了。
构造函数:
private System() {
}
可以看到System类就一个构造函数,而且被私有化了,不允许我们创建对象。如果要使用这个类,只能使用类中的一些静态成员。这也是java中单例模式的雏形,把构造私有化,然后对外提供一个内部创建的唯一的对象,就形成单例了。
其它函数:
setIn:
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
//对setIo进行安全检查
private static void checkIO() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setIO"));
}
}
//给System.in重新赋值,内部实现。
private static native void setIn0(InputStream in);
给System.in对象重新赋值。即当调用了些方法后,我们再使用System.in的时候接收的信息不再是从键盘录入了,而是我们指定的文件或设备。点开函数可以看到,第一个函数是进行安全检查的,第二个函数是调用的本地方法。有人疑惑System.in变量不是被final修饰了吗,为什么还可以被修改。额...这个...它调用的是本地方法,由虚拟机内部实现,他是老大能不能改变他说了算。
setOut:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
给System.out重新赋值,原理同setIn。比如我们想让System.out.println()把内容输出到文件,就可以调用这个方法,指定输出文件。
setErr:
public static void setErr(PrintStream err) {
checkIO();
setErr0(err);
}
给System.err重新赋值,原理同setOut。
console:
public static Console console() {
if (cons == null) {
synchronized (System.class) {
cons = sun.misc.SharedSecrets.getJavaIOAccess().console();
}
}
return cons;
}
返回唯一的Console对象,这个对象是跟Java虚拟机相关联的。可以看到这里使用了synchronized代码块,避免多线程调用的时候返回多个Console对象。
inheritedChannel:
public static Channel inheritedChannel() throws IOException {
return SelectorProvider.provider().inheritedChannel();
}
返回从创建此 java 虚拟机的实体中继承的通道。如果有则返回,没有返回null。
SecurityManager设置与获取:
public static void setSecurityManager(final SecurityManager s) {
try {
s.checkPackageAccess("java.lang");
} catch (Exception e) {
// no-op
}
setSecurityManager0(s);
}
public static SecurityManager getSecurityManager() {
return security;
}
设置和获取java的安全管理器,当运行未知的Java程序的时候,该程序可能有操作如删除系统文件、重启系统等,为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。
currentTimeMillis:
public static native long currentTimeMillis();
获取当前系统时间的毫秒数。1秒=1000毫秒(ms);
nanoTime:
public static native long nanoTime();
获取当前系统时间的纳秒数。1秒=1,000,000,000 纳秒(ns);
arraycopy:
public static native void arraycopy(
Object src,//源数组
int srcPos, //源数组要复制的起始位置
Object dest, //目标数组
int destPos, //目标数组的起始位置
int length//复制的长度
);
复制数组,从一个明确的数组中复制数组元素到另一个数组中。System.arraycopy采用的是浅复制,使用的时候注意一下。
identityHashCode:
public static native int identityHashCode(Object x);
获取对象的hashCode,一般可通过对象的hashCode()方法获取对象的hashCode,但有些对象重写了hashCode()方法,那么想要获取对象的hashCode时就要使用本方法了。
props:
private static Properties props;
private static native Properties initProperties(Properties props);
public static void setProperties(Properties props) {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertiesAccess();
}
if (props == null) {
props = new Properties();
initProperties(props);
}
System.props = props;
}
public static Properties getProperties() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertiesAccess();
}
return props;
}
props是用于承装java的系统变量的,initProperties是初始化props对象,本地方法实现。setProperties()重新设置系统的Properties对象。getProperties()方法用于获取Properties对象。不论是设置还是获取都先会进行安全检查,看是否对系统属性有安全限制。获取Properties对象后可重新设值取值。
public static String setProperty(String key, String value) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new PropertyPermission(key,
SecurityConstants.PROPERTY_WRITE_ACTION));
}
return (String) props.setProperty(key, value);
}
设置系统属性到Properties对象中,如果已经存在则覆盖原有属性,并返回原有属性。该对象是全局的,在程序中到处都可以调用。
getProperty:
public static String getProperty(String key) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key);
}
return props.getProperty(key);
}
从Properties中获取系统属性,如果有则返回,如果没有返回null。
getProperty(String key, String def):
public static String getProperty(String key, String def) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key);
}
return props.getProperty(key, def);
}
从Properties中获取系统属性,如果有则返回,如果没有返回默认值,第二个参数为默认值。
clearProperty:
public static String clearProperty(String key) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new PropertyPermission(key, "write"));
}
return (String) props.remove(key);
}
用于清除系统属性,并返回原属性值。
lineSeparator:
public static String lineSeparator() {
return lineSeparator;
}
获取系统的换行符widows系统返回"\r\n",UNIX系统返回"\n"。
getenv:
public static java.util.Map<String,String> getenv() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("getenv.*"));
}
return ProcessEnvironment.getenv();
}
获取java程序环境信息,如LOCALAPPDATA,JAVA_HOME等。返回的是一个Map对象。
exit:
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
结束当前正在运行中的java虚拟机。参数0代表正常退出,非0代表异常退出。
gc:
public static void gc() {
Runtime.getRuntime().gc();
}
该方法用于告知虚拟机进行垃圾回收。垃圾收集器将运行以回收未使用的内存空间。注意这一步只是通知虚拟机要进行垃圾回收操作,虚拟机并不一定立即进行回收操作。
runFinalization:
public static void runFinalization() {
Runtime.getRuntime().runFinalization();
}
我们知道Object对象不有一个finalize方法,当进行垃圾回收时,对象被回收前会执行finalize方法。调用runFinalization方法运行处于挂起终止状态的所有对象的终止方法。Java虚拟机已经尽最大努力去完成所有未执行的终止方法。
load:
public static void load(String filename) {
Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
用于装载指定名称filename的系统库文件。filename必需是绝对路径。
loadLibrary:
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
加载系统中的本地库,如果是Windows系统加载的是xxx.dll,如果是unix系统加载的是xxx.so,注意该方法不能包含文件的扩展名,但该文件必需在JVM属性java.library.path所指向的路径中。
mapLibraryName:
public static native String mapLibraryName(String libname);
将一个库名称映射到特定于平台的、表示本机库的字符串中。有点抽象,比如你想映射mapLibraryName这个库名,映射到Windows平台就是mapLibraryName.dll映射到Unix平台就是mapLibraryName.so