입출력 자원(IO Resource)
프로그램은 운영체제에 자원을 할당받아 동작합니다.
자원에는 연산을 담당하는 CPU, 주기억장치, 보조기억 장치 등이 있고 이러한 자원들을 할당 받게 됩니다.
이러한 자원들은 여러 프로그램들이 필요로 하는 것이지만 한정되어 있기 때문에 여러 프로그램에게 공평하게 분배되어야 합니다.

그러나 프로그램의 오류나 프로그램의 안좋은 동작방식에 의해 자원들을 할당받기만 하고 반납해주지 않는다면 다른 프로그램은 자원을 사용하지 못하게 될 것입니다.
또한 점점 더 많은 자원을 요구하여 결국 모든 자원을 독점하고 사용하게 된다면 운영체제의 자원 고갈로 인해 치명적인 문제가 발생할 수 있습니다.

이 중 가장 많은 문제가 발생하는 것이 입출력 자원입니다.
프로그램은 수많은 대상과의 입출력을 수행하게 됩니다.
보조 기억 장치와의 입출력 네트워크상의 다른 컴퓨터와의 입출력, 서버 프로그램인 경우에는 다수의 클라이언트와의 입출력을 수행합니다.
입출력을 수행하기 위해서는 스트림이라 불리우는 데이터를 주고 받는 통로를 열어주어야 하는데 이러한 통로가 입출력 자원입니다.

이러한 입출력들은 자원을 많이 소모 하므로 입출력이 완료되면 꼭 해당 자원을 돌려주어야 합니다.


그리고 자원을 돌려줄 때 입출력 스트림(통로)을 닫아준다는 개념에서 "close 한다" 라고 하고 하며 자바에서 입출력 자원을 얻을 때에는 open() 메서드를 그리고 자원을 반납할 때 close() 메서드를 사용하게 됩니다.
open()메서드의 경우 입출력 객체를 생성자로 만들 때 내부적으로 수행하게 되며 close() 메서드는 개발자가 직접 처리해 주어야 합니다.


자바 입출력 예외처리
그런데 입출력을 수행하는 도중 예외가 발생하는 경우는 어떻게 될까요?
입출력 도중 예외가 발생하게 됐을 때 특별한 처리를 해주지 않는다면 입출력을 수행하던 스트림이 open 되어진 상태로 남겨지게 됩니다.
한 두번의 예외라면 괜찮겠지만 동시에 수많은 입출력 처리를 하는 프로그램이라면 예외가 발생할 때마다 close 되지 않은 스트림이 쌓이게 되고 언젠가는 자원부족 특히 메모리 부족으로 인해 프로그램이 멈춰버릴 것입니다.

이렇게 메모리가 점점 고갈되는 것을 메모리가 새어 나온다 하여 메모리 누수(Memory Leak) 현상이라고도 합니다.
입출력으로 인한 메모리 누수를 방지하기 위해서는 예외처리를 잘 해주어 예외가 발생한 경우에도 자원이 close 되어 잘 반납될 수 있도록 처리해  주어야 합니다.

다음은 입출력시의 전형적인 예외처리 구조입니다.
소스를 보면 실제 입출력 핵심 코드보다 예외처리에 관한 소스가 훨씬 많다는 것을 느낄 수 있습니다.

이러한 구조로 입출력 처리가 많은 프로그램을 작성하게 된다면 소스가 엄청나게 복잡해지게 됩니다.


FileOutputStream out = null;
try {
    out = new FileOutputStream("exFile.txt");
    //...이후 입출력 로직 처리...
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    
    if(out != null) { //스트림이 null인지 체크
        try {
            out.close(); //close 하다가 예외가 발생할 수 있다.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


예외시 자원을 자동으로 닫아주는 try - with - resources 문
자바7 버전에서 try - with - resources 구문이 추가되었습니다.
위에서 기존의 입출력 처리시의 예외처리는 다음과 같이 바뀌어 사용됩니다.
try - with - resources 구문을 사용하게 되면 입출력 처리시 예외가 발생하는 경우 JVM이 자동으로 close()를 호출하여 자원을 반납시켜줍니다.
try() 안에 입출력 스트림을 생성하는 로직을 작성하는데 이때 해당 객체는 AutoCloseable 인터페이스를 구현한 객체여야 합니다.

불필요한 예외처리 구문들이 사라졌기 때문에 소스의 가독성이 훨씬 증가 된 것을 알 수 있습니다.

try(FileOutputStream out  = new FileOutputStream("exFile.txt")) {
    //...이후 입출력 로직 처리...
}catch(IOException e){
    e.printStackTrace();
}


try - with - resources 사용시 예외 정보 출력

try - with - resources 에서 발생한 예외는 기존과 똑같이 catch문으로 던져지게 되는데 이때 catch문이나 finally{}문에서 close()를 호출할 필요는 없어졌지만 예외 정보까지 자동으로 출력해 주지는 않기 때문에 예외에 대한 기록은 직접 catch문에서 처리해 주어야 합니다.



자동 close() 시에 예외가 발생한 경우
만약 close()시에도 예외가 발생했다면 발생한 예외를 입출력 처리시 발생한 예외에 담아 던져지게 됩니다.

MyException이 발생하여 JVM이 자동으로 close() 메서드를 호출했고 예외를 반납하던 도중에 CloseException이 발생됐다면 다음과 같은 처리를 하게됩니다. 
try(MyResource my  = new MyResource()) {
    my.out(); //MyException 발생
}catch(Exception e){
    e.printStackTrace();
}
javaTest.MyException
    at javaTest.MyResource.out(ExceptionTest_1.java:27)
    at javaTest.ExceptionTest_1.main(ExceptionTest_1.java:10)
    Suppressed: javaTest.CloseException
        at javaTest.MyResource.close(ExceptionTest_1.java:23)
        at javaTest.ExceptionTest_1.main(ExceptionTest_1.java:11)

예외는 최종적으로 한 개만 catch문으로 던져질 수 있으므로 입출력 처리시 발생한 예외 MyException에 close시에 발생한 CloseException을 넣어 하나로 던지게 되며 CloseException은 강제로 억제(Suppressed)되었기 때문에 Suppressed라는 문구를 예외 메세지 앞에 붙여주게 됩니다. 



AutoCloseable 인터페이스
try - with - resources 구문을 통해 자동 자원 반납을 지원하기 위해서는 해당 입출력 객체가 AutoCloseable 인터페이스를 구현해야 합니다.

그 이유는 AutoCloseable은 다음과 같이 정의되어 있는데 예외가 발생하면 JVM이 구현된 close() 메서드를 호출하기 때문입니다.
public interface AutoCloseable {
    void close() throws Exception;
}



블로그 이미지

도로락

IT, 프로그래밍, 컴퓨터 활용 정보 등을 위한 블로그

,