Java System类与系统属性

1. 絮絮叨叨

  • 以前,在学习maven属性,就有接触过Java系统属性

  • 后来,在工作中搭建开源组件本地开发环境时,有在VM Options中使用Java系统属性为应用指定properties文件

    -Dconfig=etc/config.properties
    -Dlog.levels-file=etc/log.properties
    
  • 自己的印象仍然停留在:与Java有关的场景中,-Dproperty=value就是在使用Java系统属性传递配置

  • 这些场景包括,但不限于:

    • 执行maven命令时,通过Java系统属性传递配置,以用于maven属性、profile的激活等
    • 执行Java程序时,通过Java系统属性传递值配置,以管理或助力应用程序的运行
    • 有些开源组件的代码中,有通过System.getProperty("property_name")获取系统属性的值,用于作为某个变量的默认值
  • 最近,在阅读之前的博客《maven属性》时,发现自己纯粹就是《maven实战》一书的搬运工,根本没有加入自己的理解或实战

  • 而且自己对Java系统属性的了解也很片面,这就激发了自己想要系统学习Java系统属性的想法

2. System类

  • 在学习Java系统属性前,可以先关注下系统属性的基础类System

2.1 System类无法被实例化

  • System类的类声明和构造函数如下,可知System类无法被继承,也无法被其他类实例化
    public final class System {
    	private System() {}
    }
    
  • 这样的定义忽然就让人想到了单例中,私有构造函数,以保证该类只有一个实例
  • 但仔细一看,System类与单例类有所不同:单例类中,由于其他类无法对其进行实例化,需要自我实例化;而System类中,不存在自我实例化

2.2 System类的有用字段和方法

  • 由于System类既不允许其他类对其进行实例化,也不自我实例化,因此System类的有用字段和方法都是public static

    1. 标准输入、标准输出、错误输出stream,大名鼎鼎的System.inSystem.outSystem.err

      public final static InputStream in = null;
      public final static PrintStream out = null;
      public final static PrintStream err = null;
      
    2. 访问外部定义的属性(Java系统属性)和环境变量的方法,且环境变量只能访问、无权限设置,而Java系统属性可以访问并设置

      // 访问、设置Java系统属性的方法
      public static Properties getProperties()
      public static String getProperty(String key)
      public static String getProperty(String key, String def)
      public static void setProperties(Properties props)
      public static String setProperty(String key, String value)
      
      // 访问环境变量的方法,环境变量
      public static java.util.Map<String,String> getenv()
      public static String getenv(String name)
      
    3. 加载文件和library的方法(从方法注释来看,二者都可以加载native library,但前者是library文件的绝对路径,后者可以只指定library名,虚拟机会自动到系统lib目录去加载library)

      public static void load(String filename)
      public static void loadLibrary(String libname)
      
    4. 快速复制数组部分元素的使用方法,大名鼎鼎的的native方法System.arraycopy()

      public static native void arraycopy(Object src, int  srcPos, Object dest, int destPos,int length)
      
  • 除此之外还有很多其他的有用方法:

    // System.exit(n),用于快速终止当前正在运行的JVM
    public static void exit(int status)
    // 获取当前的时间与UTC时间的1970-01-01 00:00:00差值,计算机的本地系统时间
    // 不同的机器系统时间有差异,要是想要准确的耗时,最好使用nanoTime()
    public static native long currentTimeMillis()
    // JVM的高分辨率时间源,按照各路大神的说法,以及开源组件的源代码来看,该方法比较时间先后,比currentTimeMillis()靠谱
    public static native long nanoTime();
    

2.3 System类的初始化

  • System类无法实例化,但并不代表其不用初始化。例如,上面提到的in、out、err等重要字段,一开始就是未初始化的

  • 线程初始化后(),会调用initializeSystemClass()方法初始化System类。注意: 这里的线程是应用程序的main线程

    private static void initializeSystemClass() {
    	// 初始化与系统属性有关的props字段
        props = new Properties();
        // native方法,此时的props中没有任何属性,需要由JVM完成内置的系统属性的添加
        initProperties(props);  
    	... // 其他的一些初始化操作
    	
        // 获取系统属性line.separator的值,用于初始化lineSeparator字段
        lineSeparator = props.getProperty("line.separator");
        // 设置系统属性:java.version、java.runtime.version、java.runtime.name三个系统属性
        sun.misc.Version.init();
        
        // 通过native方法,实现in、out、err的初始化
        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")));
    	... // 其他的一些初始化操作
    
        // The main thread is not added to its thread group in the same way as other threads; 
        // we must do it ourselves here. 从该注释可以看出,这里的线程是启动应用程序的main线程
        Thread current = Thread.currentThread();
        current.getThreadGroup().add(current);
    	... // 其他的一些初始化操作
    }
    

看到这里,自己有个疑问:in、out、err都是final static,后续还能再赋值吗?

  • 自定义Test类,模拟System类对out的初始化流程,发现IDE就已经提示错误了
  • 猜测: native方法更加底层,可以完成final变量的重新赋值,且与in、out、err有关的setter方法,也佐证了这个猜想
    Reassigns the “standard” output stream.
    // 以下方法可以重分配这些stream
    public static void setIn(InputStream in) {
        checkIO();
        setIn0(in);
    }
    
    public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
    }
    
    public static void setErr(PrintStream err) {
        checkIO();
        setErr0(err);
    }
    

initializeSystemClass()是private方法,如何能在线程初始化后被调用?

  • 在System类的开头有如下代码

    // register the natives via the static initializer. VM will invoke the initializeSystemClass method 
    // to complete the initialization for this class separated from clinit. 
    private static native void registerNatives();
    static {
        registerNatives();
    }
    
  • 根据注释,推测System类的初始化流程如下:

    • System类加载后,会执行静态语句块。其中,静态语句块调用了registerNatives()这个native方法
    • registerNatives()方法中,存在对initializeSystemClass()方法的调用
    • 至此,整个System类的初始化完成

3. Java系统属性

  • 从initializeSystemClass()方法可知,System类中,使用props字段存储系统属性

    private static Properties props;
    

3.1 内置的Java系统属性有哪些?

  • 所谓内置的系统属性,就是通过initProperties()这个native方法初始化的系统属性(笔者自创 😂 )

    private static native Properties initProperties(Properties props)
    
  • System类注释中,有两个地方罗列了内置的Java内置系统属性:定义props字段时、定义getProperties()方法时

  • 两个列表大同小异,现在展示一些笔者自认为比较重要的Java系统属性

    属性名含义
    java.versionJRE version,笔者的是1.8.0_192
    java.runtime.version包含build identifier的JRE version,笔者的是`1.8.0_192-b12
    java.homeJava的安装目录,笔者的是/path/to/jdk1.8.0_192.jdk/Contents/Home/jre
    java.class.pathJava classPath
    java.library.path加载library的路径列表
    os.nameOS名,例如Mac OS X
    os.archOS架构,例如x86_64
    os.versionOS版本,例如10.16
    user.name用户名,登录系统时的名字,如sunrise
    user.home用户home目录,mac的/Users/sunrise
    user.dir用户当前的working directory,笔者发现IDE运行程序时,working dir一般是项目的根目录,如/Users/sunrise/IdeaProjects/study-project
    file.separator文件分隔符,/ on UNIX, \ on Windows。就是文件路径的分隔符,windows的C:\Documents\ ,unix的/Users/sunrise
    line.separator行分隔符,\n on UNIX,\r\n on Windows
    path.separatorjava.class.path中的路径分隔符,; for windows , : for Unix
  • 从表格罗列的部分系统属性可以看出,系统属性的值并非固定的,会受操作系统(OS)、文件系统、安装的JDK等的影响

  • 以path.separator为例,mac的path.separator为:,而windows的则是;,可以从安装JDK,配置环境变量时看出
    在这里插入图片描述

3.2 获取系统属性

3.2.1 获取所有的系统属性

  • 可以通过getProperties()方法,获取所有的系统属性。

    public static Properties getProperties() {
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPropertiesAccess();
        }
    
        return props;
    }
    
  • 若存在SecurityManager,会先调用SecurityManager的checkPropertiesAccess()方法检测是否允许访问系统属性

  • 若不允许访问系统属性,则会抛出SecurityException

  • 想要了解当前系统,有哪些系统属性、以及这些系统属性的值,可以通过getProperties()方法实现

    public static void main(String[] args) {
        Properties properties = System.getProperties();
        properties.list(System.out);
    }
    

3.2.2 访问指定的系统属性

  • 可以通过getProperty()方法访问指定的系统属性,并且支持带默认值的getProperty()方法

    public static String getProperty(String key) {
        checkKey(key); // 要求key不为null或空字符串
        ... // 基于SecurityManager的权限校验,省略
    
        return props.getProperty(key);
    }
    
    public static String getProperty(String key, String def) {
        ... // 前半部分代码,与getProperty(String key)方法一致,省略
    
        return props.getProperty(key, def);
    }
    
  • 开源组件Presto,通过获取os有关的系统属性,以检测运行环境是否满足要求

    private static void verifyOsArchitecture()
    {
        String osName = StandardSystemProperty.OS_NAME.value(); // 等价于Syste.getPorperty("os.name")
        String osArch = StandardSystemProperty.OS_ARCH.value();// 等价于Syste.getPorperty("os.arch")
        if ("Linux".equals(osName)) {
            if (!"amd64".equals(osArch) && !"ppc64le".equals(osArch)) {
                failRequirement("Presto requires amd64 or ppc64le on Linux (found %s)", osArch);
            }
            if ("ppc64le".equals(osArch)) {
                warnRequirement("Support for the POWER architecture is experimental");
            }
        }
        else if ("Mac OS X".equals(osName)) {
            if (!"x86_64".equals(osArch)) {
                failRequirement("Presto requires x86_64 on Mac OS X (found %s)", osArch);
            }
        }
        else {
            failRequirement("Presto requires Linux or Mac OS X (found %s)", osName);
        }
    }
    

3.2.3 不完善的checkKey()

  • checkKey()方法的源码如下,它只校验了key为null或空字符串的情况,并未校验key包含多个空白字符的情况
    private static void checkKey(String key) {
        if (key == null) {
            throw new NullPointerException("key can't be null");
        }
        if (key.equals("")) {
            throw new IllegalArgumentException("key can't be empty");
        }
    }
    
  • 笔者认为这里的校验并不完善,包含多个空白字符的key,也不应该被允许
    public static void main(String[] args) {
        System.setProperty("  ", "32");
        System.out.println(System.getProperty("  "));
    }
    

3.3 设置系统属性

3.1 覆盖已有的系统属性集

  • 通过setProperties()方法,可以以覆盖的形式设置新的系统属性集

  • 若传入的系统属性集合为null,则会调用initProperties()这个native方法,初始化为内置的系统属性集

    public static void setProperties(Properties props) {
        ... // 基于SecurityManager的权限校验省略
        if (props == null) {
            props = new Properties();
            initProperties(props);
        }
        System.props = props;
    }
    
  • 下面的代码,先设置一个非null的系统属性集,再设置一个为null的系统属性集

    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.setProperty("name", "sunrise");
        properties.setProperty("age", "27");
        // 覆盖System初始化时,设置的内置系统属性集
        System.setProperties(properties);
        System.getProperties().list(System.out);
    
        // 设置一个null的系统属性值,会自动转为设置内置的系统属性集
        System.setProperties(null);
        System.getProperties().list(System.out);
    }
    
  • 执行结果如下:

3.3.2 更改或新增系统属性

  • 想要修改某个系统属性,或添加自定义的系统属性,可以通过setProperty()实现

    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)); // 这里的check条件发生了变化
            }
    
            return (String) props.setProperty(key, value);
        }
    
  • 下面的代码,通过setProperty()修改了内置系统属性os.name的值,并新增了系统属性log.level.config

    public static void main(String[] args) {
        // 修改内置的系统属性
        System.out.printf("os.name: %s\n", System.getProperty("os.name"));
        System.setProperty("os.name", "mac os");
        System.out.printf("after update, os.name: %s\n", System.getProperty("os.name"));
        // 新增自定义的系统属性
        System.setProperty("log.level.config", "etc/log.config");
        System.out.printf("log.level.config: %s", System.getProperty("log.level.config"));
    }
    

3.4 移除系统属性

  • 系统属性除了可以覆盖、修改、新增,还可以移除

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

4. 自定义系统属性

  • 刚才,setProperty()方法的使用代码示例,就展示了如何自定义系统属性

  • 除了通过setProperty()方法显式地自定义系统属性,还可以通过-Dproperty=value这个Java命令行选项自定义系统属性

  • property表示属性名,不能带空格;value表示属性值,若value中存在空格,需要使用" "将其括起来

  • 执行main方法时,配置VM选项,传入name和address两个自定义系统属性

  • 在代码中,获取通过Java命令行选项自定义的系统属性

    public static void main(String[] args) {
        // 获取通过-Dproperty=value自定义的系统属性的值
        System.out.printf("name: %s\n", System.getProperty("name"));
        System.out.printf("address: %s\n", System.getProperty("address"));
        // 为=未定义,返回默认值
        System.out.printf("tel: %s\n", System.getProperty("tel", "010-6666888"));
        // 修改name属性
        System.setProperty("name", "jack");
        System.out.printf("name: %s\n", System.getProperty("name"));
    }
    
  • 执行结果如下:

5. 后记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值