프로그래밍/Java

[Java] JDK 부수기 - (2) java.lang.System - 4. getProperties

Churnobyl 2023. 11. 28. 14:16
728x90
반응형


4. getProperties

JVM의 시스템 속성을 리턴한다.

 

JVM 뜯어보기

 System.getProperties() 메서드는 JVM과 자바 애플리케이션 환경에 대한 설정을 담고 있는 Properties 객체를 리턴한다. 이 Properties 객체는 JVM이 시작될 때 설정되며 'java -DpropertyKey=value' 형식의 명령줄 인수를 이용해 시스템 속성을 설정해 줄 수도 있다. 이 글을 진행하면서 알게 되겠지만 System.setProperty("propertyKey", "value") 메서드를 사용해 시스템 속성을 새롭게 설정할 수도 있다. 시스템 속성에는 대표적으로 자바의 버전이나 CPU 정보, OS정보, OS의 File 구분자(Windows의 \, Linux의 /), OS의 행 구분자(Windows의 \r\n, Linux의 \n) 등이 있다. 이러한 시스템 속성 정보를 사용하면 OS Specific한 자바 애플리케이션을 작성할 수 있다. System 클래스의 Properties 관련 메서드들을 모아서 보자.

 

// src/java.base/java/lang/System.java

public final class System {

    private static Properties props;
    
    public static Properties getProperties() {
        @SuppressWarnings("removal")
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPropertiesAccess();
        }

        return props;
    }
    
    public static String lineSeparator() {
        return lineSeparator;
    }
    
    private static String lineSeparator;
    
    public static void setProperties(Properties props) {
        @SuppressWarnings("removal")
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPropertiesAccess();
        }

        if (props == null) {
            Map<String, String> tempProps = SystemProps.initProperties();
            VersionProps.init(tempProps);
            props = createProperties(tempProps);
        }
        System.props = props;
    }
    
    public static String getProperty(String key) {
        checkKey(key);
        @SuppressWarnings("removal")
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPropertyAccess(key);
        }

        return props.getProperty(key);
    }
    
    public static String getProperty(String key, String def) {
        checkKey(key);
        @SuppressWarnings("removal")
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPropertyAccess(key);
        }

        return props.getProperty(key, def);
    }
    
    public static String clearProperty(String key) {
        checkKey(key);
        @SuppressWarnings("removal")
        SecurityManager sm = getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new PropertyPermission(key, "write"));
        }

        return (String) props.remove(key);
    }
    
    private static void checkKey(String key) {
        if (key == null) {
            throw new NullPointerException("key can't be null");
        }
        if (key.isEmpty()) {
            throw new IllegalArgumentException("key can't be empty");
        }
    }
    
    private static Properties createProperties(Map<String, String> initialProps) {
        Properties properties = new Properties(initialProps.size());
        for (var entry : initialProps.entrySet()) {
            String prop = entry.getKey();
            switch (prop) {
                // Do not add private system properties to the Properties
                case "sun.nio.MaxDirectMemorySize":
                case "sun.nio.PageAlignDirectMemory":
                    // used by java.lang.Integer.IntegerCache
                case "java.lang.Integer.IntegerCache.high":
                    // used by sun.launcher.LauncherHelper
                case "sun.java.launcher.diag":
                    // used by jdk.internal.loader.ClassLoaders
                case "jdk.boot.class.path.append":
                    break;
                default:
                    properties.put(prop, entry.getValue());
            }
        }
        return properties;
    }
    
    private static void initPhase1() {
        
        Map<String, String> tempProps = SystemProps.initProperties();
        VersionProps.init(tempProps);
        
        props = createProperties(tempProps);
        
        lineSeparator = props.getProperty("line.separator");
        
    }
}

 

 System 클래스에서 Properties 객체에 관련한 멤버들을 모두 정리했다. 가독성을 위해 주석은 조금 정리하고 Properties 객체로부터 바로 얻을 수 있는 lineSeparator() 메서드도 포함했으며, 스레드에 의한 System 초기화 과정 중 하나인 initPhase1() 메서드에 관련해서는 Properties 객체에 관련한 부분만 추출했다. System 초기화 과정은 총 Phase1, 2, 3 세 단계로 이루어져 있으며 더 자세한 내용은 추후에 스레드를 공부할 때 함께 공부하려고 한다.

 

 우선 자바 애플리케이션을 실행하기 위해 JVM이 실행되면 클래드 로더 시스템이 실행되고 관련 로딩 작업 후에 여러 스레드가 실행된다. 이때 System 초기화 과정이 동반되며 그 과정은 위의 initPhase1를 포함한 initPhase2, initPhase3 세 단계를 실행하는 과정이다. 그 중 JVM의 시스템 속성 정보를 담고 있는 Properties객체를 초기화하는 과정을 포함하는 initPhase1() 메서드을 살펴보자.

 

 시스템 초기화를 위해 스레드가 initPhase1() 메서드를 실행하면 먼저 SystemProps클래스의 initProperties() 메서드를 실행해 tempProps에 저장한다. initProperties() 메서드는 네이티브 속성과 명령줄 인수 속성으로부터 시스템 속성을 만들고 초기화하는 메서드다. SystemProps 클래스를 찾아보면 내부적으로 Raw라는 객체를 만들고, 네이티브 메서드로 OS속성을 String[] 타입으로 가져온 뒤 인덱스를 이용해 하나씩 맵 객체에 삽입해 리턴하는 단순하고 긴 과정이 있지만 여기서는 생략하고 initProperties() 메서드는 OS속성을 맵 객체로 리턴한다는 것만 기억하자.

 

 다음으로 VersionProps클래스의 init() 메서드를 실행해 tempProps 맵 객체에 자바에 관련한 속성을 추가한다. 그 다음으로는 createProperties() 메서드를 호출해 tempProps 맵 객체를 Properties 객체로 만들어 props에 저장한다. 마지막으로 props 객체로부터 "line.separator" 속성값을 통해 OS의 행 구분자를 찾아 lineSeparator 멤버변수에 저장한다. 이 전체적인 과정이 JVM이 시작되면서 일어난다.

 

 그럼 Properties객체는 어떤 객체일까. createProperties() 메서드를 살펴보면 JDK 내부에서만 사용되며 외부에 노출될 필요가 없는 속성들(예를 들어 sun.nio.MaxDirectMemorySize; JVM이 사용할 수 있는 최대 Direct Memory 크기)를 걸러낸 나머지를 key-value 형식으로 저장하는 걸로 봐서 맵 타입의 일종으로 예상할 수 있다.

 

사용해보기

import java.util.Properties;

public class Main {
    public static void main(String[] args) {

        Properties props = System.getProperties();
        props.list(System.out);
        
        System.out.println("=============================");

        props.put("testProp", "5555");
        props.list(System.out);

        System.out.println("=============================");

        Properties newProps = new Properties();
        newProps.setProperty("onlyOneProp", "1");
        System.setProperties(newProps);

        props = System.getProperties();
        props.list(System.out);
    }
}

 

java -Dcommand.line.prop=1234 Main

 

-- listing properties --
java.specification.version=17
sun.cpu.isalist=amd64
sun.jnu.encoding=MS949
os.name=Windows 10
command.line.prop=1234
line.separator=

=============================
-- listing properties --
java.specification.version=17
sun.cpu.isalist=amd64
sun.jnu.encoding=MS949
os.name=Windows 10
command.line.prop=1234
line.separator=

testProp=5555
=============================
-- listing properties --
onlyOneProp=1

 

 System의 Properties 관련 메서드들이 어떻게 동작하는지 확인하기 위해 간단한 예제를 만들었다. 명령줄 인수를 추가했을 때 결과를 확인하기 위해 위 코드는 컴파일 후에 command.line.prop=1234라는 인수와 함께 실행시켜 주었다.

 

 결과는 크게 세 번의 출력을 가진다. 첫번째로는 System.getProperties() 메서드로 얻은 결과를 그대로 출력한 것이다. 사용자 이름이나 기타 민감한 정보들은 출력에서 제외했다. 첫번째 결과를 보면 자바 버전이나 CPU ISA, 인코딩OS 정보 그리고 명령줄 인수에서 추가해주었던 정보도 함께 저장되어 있는 것을 볼 수 있다. 물론 line.separator도 함께 출력됐다.

 

 두번째로는 시스템의 Properties 객체에 testProp이라는 key와 5555라는 value를 추가해준 경우다. 이 경우에도 다음과 같이 잘 출력되는 것을 볼 수 있다. Main 메서드의 props 변수의 값에만 추가된 것이 아니라, props변수는 Properties 객체를 참조하는 참조변수이므로 System.getProperties() 메서드를 다시 호출하더라도 같은 결과를 얻을 수 있다.

 

마지막으로는 System.setProperty() 메서드를 이용해 새로운 Properties 객체 newProps를 넘겨주었다. 물론 이렇게 쓰지도 않고 위험한 방법이지만 System.getProperties() 메서드로 다시 가져온 결과를 보면 newProps로 대체된 것을 볼 수 있다.

 

 위 결과를 출력할 때 사용한 props.list(System.out)은 Properties 클래스에서 제공하는 메서드로 시스템 속성을 한줄씩 출력 장치를 통해 출력할 수 있다.

반응형