宝,我在看System, 什么System?每天用的System

806 阅读6分钟

开篇寄语

哈喽,大家好,我是尘心,一个每天都在电脑前酣战淋漓的男人。说起System类,想必大家都不陌生,从一个hello world程序开始就在使用:System.out.print("hello world!")。日常开发中,也经常会用到System类,但是我们都没有去探究它,那么我们今天就研究一下System类的源码。

本文使用Java 8 作为研究版本。

Native关键字

native关键字在jdk源码层面是比较常见的,那么我们就非常有必要搞清楚,native的作用。native方法我们一般称为本地方法,在java源程序中以关键字 “native”声明,并不提供函数体。既然有声明则必定有实现。native方法的实现一般使用C/C++语言在另外的文件中编写,编写的规则遵循Java本地接口的规范(也就是我们说的JNI)。简而言之,就是Java中声明的可调用使用C/C++实现的方法。C/C++语言是可以直接操作计算机系统内存的。

源码剖析System类

System 位于 java.lang包下,被final修饰。不允许我们自己创建System对象。

开头有一段静态代码块,用于注册Natives(本地方法)。根据注释可以看出,VM将调用 initializeSystemClass方法完成这个类的初始化。registerNatives() 方法被native修饰,native表示该方法为本地方法,调用底层方法完成注册。

    /* 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();
    }

System类初始化

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")));

    loadLibrary("zip");

    Terminator.setup();

    sun.misc.VM.initializeOSEnvironment();

    Thread current = Thread.currentThread();
    current.getThreadGroup().add(current);

    setJavaLangAccess();
    sun.misc.VM.booted();
}

初始化中做了以下操作:

  1. 创建一个Properties配置对象,调用initProperties(props);方法进行初始化。initProperties方法也是本地方法,底层使用C/C++实现Java运行环境配置的初始化操作。我已经制作成表格,所有可以取的属性都在表格中。当然,运行中还会有其他属性设置,用户也可添加属性。
KeyDescription of Associated Value
java.versionJava Runtime Environment version
java.vendorJava Runtime Environment vendor
java.vendor.urlJava vendor URL
java.homeJava installation directory
java.vm.specification.versionJava Virtual Machine specification version
java.vm.specification.vendorJava Virtual Machine specification vendor
java.vm.specification.nameJava Virtual Machine specification name
java.vm.versionJava Virtual Machine implementation version
java.vm.vendorJava Virtual Machine implementation vendor
java.vm.nameJava Virtual Machine implementation name
java.specification.versionJava Runtime Environment specification version
java.specification.vendorJava Runtime Environment specification vendor
java.specification.nameJava Runtime Environment specification name
java.class.versionJava class format version number
java.class.pathJava class path
java.library.pathList of paths to search when loading libraries
java.io.tmpdirDefault temp file path
java.compilerName of JIT compiler to use
java.ext.dirsPath of extension directory or directories Deprecated. This property, and the mechanism which implements it, may be removed in a future release.
os.nameOperating system name
os.archOperating system architecture
os.versionOperating system version
file.separatorFile separator ("/" on UNIX)
path.separatorPath separator (":" on UNIX)
line.separatorLine separator ("\n" on UNIX)
user.nameUser's account name
user.homeUser's home directory
user.dirUser's current working directory

看到这里大家就大概明白,为什么获取用户信息的时候,可以直接从System类中获取。例如:

// 获取用户home 目录
System.getProperty("user.home");
// 获取用户当前工作目录
System.getProperty("user.dir");
  1. 调用sun.misc.VM.saveAndRemoveProperties(props); 方法保存系统配置

  2. 调用sun.misc.Version.init(); 初始化版本信息

  3. 初始化一些输入,标准输出,错误输出流,编码从系统属性中取。

        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")));

成员属性中定义了out,err等输出流,在初始化时会被赋值(也就是上面那段代码),这就是我们经常使用的 System.out.print()

    public final static PrintStream out = null;
    public final static PrintStream err = null;

我们看一下PrintStream的println()方法,发现其底层还是加了synchronized 关键字,保障线程安全问题。

    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
  1. 加载jdk相关资源
loadLibrary("zip"); // 其中还有很多资源通过这种方加载, 例如:awt,jni等等
  1. 安装Java信号处理器: HUP,TERM,INT
// Setup Java signal handlers for HUP, TERM, and INT (where available).
Terminator.setup();
  1. 初始化一些操作系统环境
sun.misc.VM.initializeOSEnvironment();
  1. main线程加入到它自己的线程组
// 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);

// 注释大致意思:主线程不会像其他线程一样加入到它的线程组中;我们必须在这里自己做。
  1. 注册共享密钥. 主要是注册一些共享信息,如常量池等
// register shared secrets
setJavaLangAccess();
  1. 在初始化期间调用的子系统可以调用 sun.misc.VM.isBooted() 以避免做应该等到应用程序类加载器设置完成的事情。
// Subsystems that are invoked during initialization can invoke
// sun.misc.VM.isBooted() in order to avoid doing things that should
// wait until the application class loader has been set up.
// IMPORTANT: Ensure that this remains the last initialization action!
sun.misc.VM.booted();

System常用API

这里我们总结以下System的一些API,方便大家在开发过程中使用:

方法说明
public static void setIn(InputStream in)设置System打印的输入流
public static void setOut(PrintStream out)设置System打印的输出流
public static void setErr(PrintStream err)设置System打印的错误输出流
public static native long currentTimeMillis();获取当前毫秒值
public static native long nanoTime();获取当前纳秒值
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);数组拷贝
public static native int identityHashCode(Object x);计算对象的hashcode值
public static Properties getProperties()获取JVM属性对象
public static String lineSeparator()获取系统行分割符
public static String setProperty(String key, String value)设置属性
public static String getProperty(String key, String def)获取属性,没有则取默认值def
public static String getProperty(String key)获取key对应属性
public static String clearProperty(String key)删除某个属性
public static Map<String,String> getenv()获取运行环境,计算机名称,JDK路径等等,Maven路径
public static void exit(int status)退出
public static void gc()调用GC

输入输出方法就不写上去了,毕竟天天用。

总结

System类是我们常用的一个工具类,用于获取JVM运行时系统的一些属性,调用一些较为底层的操作,如gc();,exit();等等。其底层很多使用natvie修饰的方法,可以直接操作计算机系统内存。经常使用的东西,我们往往会忽视它的存在,了解他或许会让你更加如鱼得水。我是尘心,如果觉得文章对你有帮助,可以点赞给更多的人看到。公众号:(GitHub严选),分享那些有深度的内容。