美文网首页
浅谈Java系列:System类之初始化

浅谈Java系列:System类之初始化

作者: AmyXYC | 来源:发表于2019-08-20 06:02 被阅读0次

初识System类

System类是从JDK1.0就被定义了的一个类,本篇所有解读都是基于OpenJdk8而来。System类由final修饰符修饰,所以它不能被继承扩展,而System类只有一个私有的无参构造函数,所以它不能被实例化,我们只能使用这个类里的静态方法。

public final class System {    

    //此处省略代码XX行        

    /* Don’t let anyone instantiate this class /    
    private System() {    
    }        

    //此处省略代码XX行

}

System类为我们提供了一些方便的工具,通过它我们可以很方便的做以下几个事儿:

  • 使用System类使用标准输入流、标准输出流和标准错误输出流
  • 使用System类访问外部定义的系统属性和环境变量
  • 通过System类动态加载动态链接库
  • 使用System类快速的复制部分数组元素

System类初始化

System类初始化可以分为两部分:本地方法初始化VM调用初始化

什么是本地方法?

一个本地方法就是由Java调用的非Java语言实现的接口,并保存在动态链接库中

为什么要用本地方法?

首先,有些层次的任务用Java实现起来比较不易,或者当我们对程序执行的效率比较在意的时候,使用本地方法可能是更好的选择,如:与非Java环境进行交互、与底层系统或硬件交换信息

其次,JVM是一个解释器,它的一部分实现就是使用的C语言,它的运行也需要操作系统的支持,与操作系统的交互就是通过本地方法

System类本地方法初始化

System类中定义了许多本地方法,Java代码要调用一个本地方法需要2个步骤:

1、 通过System.loadLibrary()方法将包含本地方法的动态链接库加载进入内存中,如在Win32环境下执行System.loadLibrary("foo")就会把foo.dll加载进内存中

2、 当Java要调用本地方法时,JVM会在已加载进内存的动态链接库中定位并链接要调用的本地方法,然后才可以执行本地方法。比如要执行Foo.g()方法,JVM会在foo.dll动态链接库中定位并链接Java_Foo_g这个本地方法

显然步骤2有些低效,Java要调用本地方法是如果能省去定位链接岂不是更好吗?registerNatives()方法就是为了代替步骤2应运而生,在许多类里都能发现它的踪影,作用都是一样的,比如:Object类、Thread类等等。

/* 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()方法在静态代码块中被调用,编译器会将静态字段和静态代码块重新整合进<clinit>()方法中,当类被加载时<clinit>()方法被调用,静态代码块得以执行。

registerNatives()方法让程序可以主动的去链接本地方法,这样JVM就不用去动态链接库中查找定位本地方法了,Java可以直接调用本地方法执行。

我们可以到Jdk源码里翻一下它的实现(不知道怎么找的看官别急,且听下回分解)

/* Only register the performance-critical methods */
static JNINativeMethod methods[] = {
    //这里主动注册链接了三个方法,分别对应着System类里的System.currentTimeMillis()、System.nanoTime()和System.arrayCopy()三个方法
    {"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)
{
    //主动注册链接操作就是在这里干的
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

System类VM初始化

System类中初始化主要通过initializeSystemClass()方法,关于initializeSystemClass方法的调用,javadoc原文有两段是这么说的


VM will invoke the initializeSystemClass method to complete
the initialization for this class separated from clinit.

Initialize the system class, Called after thread initialization.

也就是说initializeSystemClass方法是由VM来调用的,而且是在线程被初始化以后就调用了,我大概翻了一下hotspot JVM的实现,找到了这个方法被调用的地方

//这个方法负责调用System类的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类

    initialize_class(vmSymbols::java_lang_System(), CHECK_0);      //这里加载了java.lang.System类,然而还没有调用System类的initializeSystemClass方法
    initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);  //这里加载了java.lang.ThreadGroup类
    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类
    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类

    initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);   //这里加载了java.lang.reflect.Method类
    initialize_class(vmSymbols::java_lang_ref_Finalizer(),  CHECK_0);   //这里加载了java.lang.ref.Finalizer类
    call_initializeSystemClass(CHECK_0);    //这是调用了System类的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行

}

源码中调用System类的initializeSystemClass方法是在create_vm方法中,也就是JVM创建时,在加载了String、System、ThreadGroup、Thread、Class、Method、Finalizer这几个类之后,才调用了System类的initializeSystemClass方法,之后又加载了一些异常类。

initializeSystemClass方法完成了System类后续的初始化工作,主要有以下几个部分:

1)初始化外部定义的系统属性和环境变量

        props = new Properties();
        initProperties(props);  // initialized by the VM

        sun.misc.VM.saveAndRemoveProperties(props);

        lineSeparator = props.getProperty("line.separator");
        sun.misc.Version.init();

这些系统属性和环境变量一部分是通过本地方法设置的,另一部分是通过System.setProperty()方法设置的,initProperties()方法就是一个本地方法,它的实现在Jdk源码中的/src/share/native/java/lang/System.c文件中的Java_java_lang_System_initProperties方法。

初始化完成后我们将至少能通过System.getProperty()方法访问到以下属性:(这里以OpenJdk11的jshell环境举例)

|  欢迎使用 JShell -- 版本 11.0.4
|  要大致了解该版本, 请键入: /help intro

jshell> System.getProperty("java.version");      //Java版本号                            
$1 ==> "11.0.4"

jshell> System.getProperty("java.runtime.version");     //Java运行时版本号                                                                
$2 ==> "11.0.4+11-post-Ubuntu-1ubuntu218.04.3"

jshell> System.getProperty("java.runtime.name");       //Java运行时名称                                                                 
$3 ==> "OpenJDK Runtime Environment"

jshell> System.getProperty("java.vendor");             //Java供应商                                                                 
$4 ==> "Ubuntu"

jshell> System.getProperty("java.vendor.url");        //Java供应商主页                                                                 
$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版本号,55.0应该是对应着Jdk11                                                                   
$7 ==> "55.0"

jshell> System.getProperty("java.class.path");        //环境变量类路径, ClassLoader加载类时使用                                                                 
$8 ==> "."

jshell> System.getProperty("os.name");                //操作系统名称                                                                  
$9 ==> "Linux"

jshell> System.getProperty("os.arch");                //操作系统架构                                                                  
$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");              //当前用户名                                                                  
$15 ==> "mayun"

jshell> System.getProperty("user.home");              //当前用户home目录                                                                  
$16 ==> "/home/mayun"

jshell> System.getProperty("user.dir");               //当前用户工作目录,默认文件保存路径                                                                  
$17 ==> "/home/mayun"

2)初始化标准输入、输出流

        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类以后就由JVM调用了,所以当我们使用System类时,System类的in、out、err三个字段已经被初始化完成了。setIn0、setOut0和setErr0三个方法也是本地方法,实现在Jdk源码的/src/share/native/java/lang/System.c文件中,对应着Java_java_lang_System_setIn0、Java_java_lang_System_setOut0和Java_java_lang_System_setErr0三个方法。

欢迎关注我的公众号:米爸笔记,获得独家整理的学习资源和日常干货推送

相关文章

网友评论

      本文标题:浅谈Java系列:System类之初始化

      本文链接:https://www.haomeiwen.com/subject/nbaysctx.html