자바 System 클래스의 전반적인 개요와 활용 방법

Tony·2024년 7월 1일
post-thumbnail

Java의 System 클래스는 자바 개발자들에게 필수적인 유틸리티 클래스입니다. 자바를 시작하면 가장 처음 만나는 코드가 System.out.println("Hello world"); 일 것입니다. 이처럼 우리에게 익숙한 System 클래스는 텍스트 출력 뿐만 아니라 환경 변수 접근, 시스템 속성 관리, 시간 측정, 메모리 관리 등 다양한 시스템 관련 작업을 수행할 수 있습니다. 이 글에서는 System 클래스의 주요 기능, 실제 사용 사례, 그리고 주의해야 할 점들을 상세히 살펴보겠습니다.

1. System 클래스란?

System 클래스는 java.lang 패키지에 속한 final 클래스로, 생성자가 없어 인스턴스화할 수 없습니다. 그리고 모든 멤버가 static으로 선언되어 있어서 클래스 이름을 통해 직접 접근할 수 있습니다. 주로 표준 입출력 스트림, 시스템 속성 및 환경 변수 관리, 시스템 유틸리티 메서드 등을 제공합니다.


2. System 클래스의 주요 기능과 설명

표준 입출력 스트림

System.out: 표준 출력 스트림

  • 일반적인 디버깅용 텍스트를 콘솔에 출력할 때 사용됩니다.
System.out.println("Hello World");

System.err: 표준 에러 스트림

  • 오류 메시지나 경고 메시지를 콘솔에 출력할 때 사용됩니다.
  • System.out과 같은 콘솔에 출력되지만, 다른 스트림으로 분리되어 있어 별도로 리다이렉션할 수 있습니다.
System.err.println("오류 발생 지점");

System.in: 표준 입력 스트림

  • 콘솔에서 입력받을 때 사용됩니다. 보통 Scanner 클래스와 함께 사용됩니다.
Scanner scanner = new Scanner(System.in);
System.out.print("이름을 입력하세요: ");
String name = scanner.nextLine();
System.out.println("Hello, " + name + "!");


시스템 속성 및 환경 변수

getProperty(String key): 시스템 속성 값 얻기

  • 시스템 속성은 JVM과 관련된 여러가지 정보를 포함합니다.
  • 런타임에 값이 변경될 수 있기 때문에 항상 값을 가져온 후 서비스에 적용하기 전 유효성을 검증해야 합니다.
String javaVersion = System.getProperty("java.version"); // "java version" 시스템 속성 가져오기
System.out.println("User Home: " + userHome);

getenv(String name): 환경 변수 값 얻기

  • 환경 변수는 운영 체제 수준에서 설정되기 때문에 민감한 정보(API key, DB password 등)에 접근할 수도 있습니다. 이러한 민감한 정보는 암호화하거나 별도로 관리되게 하여 공격자에게 노출되는 위험을 방지해야 합니다.
  • 로그를 남길 때 민감한 정보가 포함되지 않도록 주의해야 합니다.
String userHome = System.getenv("HOME"); // "HOME" 환경변수 가져오기
System.out.println("Java Version: " + javaVersion);

setProperty(String key, String value): 시스템 속성 값 설정

System.setProperty("custom.property", "Custom Value"); // "custom.property" 시스템 속성 설정하기
String customProperty = System.getProperty("custom.property"); // "custom.property" 시스템 속성 가져오기
System.out.println("Custom Property: " + customProperty);


시간 관련 메서드

currentTimeMillis(): 1970년 1월 1일 자정부터 지금까지 흐른 시간을 밀리초 단위로 반환 (로그에 사용됨)

long currentTime = System.currentTimeMillis(); // 글 작성중인 현재 시간은 1719838804327 (Mon Jul 01 2024 22:00:04 GMT+0900 (한국 표준시))
System.out.println("현재 시간: " + currentTime + " ms");

nanoTime(): 나노초 정밀도의 시간 값 반환 (코드 실행 시간 측정에 사용됨)

long startNanoTime = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
    // 작업 수행
}
long endNanoTime = System.nanoTime();
System.out.println("소요된 시간: " + (endNanoTime - startNanoTime) + " ns");


메모리 관리

gc(): 가비지 컬렉션 실행 요청

  • 가비지 컬렉션은 JVM 내부 알고리즘에 의해 최적의 시점에 수행되도록 설계되었습니다. 그러나 이것을 명시적으로 호출하였을 때 오히려 효율적인 메모리 관리에 부정적인 영향을 미칠 수 있기 때문에 호출하지 않는 것이 권장됩니다.

runFinalization(): 파일이나 네트워크 소켓같은 시스템 리소스를 해제하거나 gc되기 전의 객체를 정리하는 finalize 메서드 실행 요청

  • 이 함수는 자바 18 이후 버전에서 제거될 예정입니다. 리소스를 정리하는 역할로 'try-with-resources' 구문을 사용할 수 있고, 객체를 정리하는 행위를 명시적으로 수행할 경우 위의 gc() 메서드와 같이 JVM이 예측하기 어려운 방식으로 실행되어 오히려 성능에 부정적인 영향을 미칠 수 있습니다.


기타 유틸리티 메서드

arraycopy(Object src, int srcPos, Object dest, int destPos, int length): 배열 복사

  • JVM이 내부적으로 최적화 한 로직으로 수행되기 때문에 직접 루프를 사용해 배열을 복사하는 것보다 성능이 훨씬 좋습니다.
  • 범위를 벗어난 인덱스에 접근하거나 잘못된 타입의 배열을 복사하려고 할 때 ArrayIndexOutOfBoundsException 또는 ArrayStoreException을 발생시켜 문제를 쉽게 찾을 수 있습니다.
int[] srcArray = {1, 2, 3, 4, 5};
int[] destArray = new int[5];
System.arraycopy(srcArray, 0, destArray, 0, srcArray.length); // srcArray의 첫 번째 요소부터 5개의 요소를 destArray의 첫 번째 위치에 복사

identityHashCode(Object x): 객체의 해시코드를 반환

  • Object.hashCode()도 객체의 해시코드를 반환하지만, Object.hashCode()는 대부분의 경우 객체의 내용을 기반으로 해시 코드를 생성하여 동등성을 비교하도록 오버라이드해서 사용됩니다. 또한 equals() 메서드와 함께 사용되며 자바 컬렉션 프레임워크(HashMap, HashSet)의 동작에 중요한 역할을 합니다.
  • 반면에 identityHashCode(Object x)는 객체의 내용과 관계없이, 객체의 고유한 식별자로서의 해시 코드를 반환합니다. 이 메서드는 오버라이드 할 수 없기 때문에 항상 객체의 동일성을 비교하는데 사용할 수 있습니다.
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.hashCode() == str2.hashCode()); // true

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(System.identityHashCode(str1) == System.identityHashCode(str2)); // false

exit(int status): JVM 종료

  • 자바 어플리케이션을 종료합니다.
  • 파일, 네트워크 등 리소스가 정상적으로 해제되지 않을 수 있고, 처리중이거나 대기중인 모든 요청이 중지됩니다.
  • 리소스, 사용자 경험, 서버 안정성 등 많은 측면에서 사용하지 말아야 할 메서드입니다.
// 명시적인 어플리케이션 종료. 사용하지 않는 것이 권장되고, 특히 웹서버의 경우 절대 사용해선 안됨
System.exit(0);


3. System.out.println의 성능 이슈와 대안

System.out.println의 동작 방식

System.out.println 메서드는 간단한 디버깅이나 로깅에 자주 사용되지만, 성능 측면에서 문제를 가지고 있습니다. println은 PrintStream 클래스의 메서드로, 동기화 방식으로 구현돼 있습니다. 따라서 멀티스레드 환경에서 병목현상이 발생할 수 있으며 콘솔 출력으로 인한 I/O 락은 전반적인 수행 속도 저하를 일으킵니다. 그리고 여러 값을 입력할 때 문자열 연결 연산이 발생하여 추가적인 오버헤드가 발생할 수 있습니다.

// 동기화 방식으로 구현된 PrintStream 클래스의 println 메서드
public void println(String x) {
        if (getClass() == PrintStream.class) {
            writeln(String.valueOf(x));
        } else {
            synchronized (this) {
                print(x);
                newLine();
            }
        }
    }

이에 대한 대안으로 로깅 프레임워크를 사용하면 문제들을 개선할 수 있습니다. 예를 들어 Logger는 비동기로 동작하기 때문에 로그 메시지가 별도의 스레드에서 처리되어, 메인 어플리케이션의 성능에 미치는 영향을 최소화 합니다. 그리고 로그 레벨 분리, 로그 메시지를 버퍼에 모았다가 일정한 조건에 의해 한번에 기록하는 버퍼링 방식 지원, 콘솔 출력 외에도 파일, 데이터베이스에 로그를 기록할 수 있기 때문에 서비스 유지보수 측면에서도 효과적입니다.

private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.debug("디버그 로그");


정리

자바의 System 클래스는 개발자에게 시스템 수준의 기능을 제공하는 강력한 도구입니다. 그러나 강력한 기능을 제공하는 만큼 일부 메서드는 안정적인 서비스 운영에 치명적인 영향을 끼칠 수 있기 때문에 신중하게 사용해야 합니다. System 클래스의 기능을 잘 이해하고 적절히 활용한다면, 더 효율적이고 견고한 Java 애플리케이션을 개발할 수 있을 것입니다.


참고

profile
기술 자체를 목적으로 하지 않고, 사용자 경험과 비즈니스 가치를 최우선으로 생각합니다.

0개의 댓글