CS 스터디 (Java) 1 - try with resources를 설명해주세요.
try-with-resources문법은 Java 7에서 안전한 자원관리를 위해 도입된 기능으로, try 블록이 종료될 때 해당 자원이 AutoCloseable 인터페이스를 구현하고 있으면 자동으로 close() 메서드가 호출되어 자원이 해제됩니다.
try-with-resources 문법은 어느 정도 이해했지만, 두 가지 궁금증이 생겼습니다.
1. 자원관리의 중요성
2. 이전 지원관리 방법과 그 차이점
1. 자원관리의 중요성
자원관리란?
: 자원관리란, 애플리케이션 실행 중에 사용하는 시스템 자원을 할당하고, 사용이 끝난 뒤 이를 적절하게 해제하여 시스템 자원의 낭비나 누수를 방지하는 작업을 의미합니다.
1). Garbage Collection의 한계
- Java는 GC(Garbage Collection)는 heap 메모리 객체만 수집하기 때문에, 외부 자원의 경우 GC 대상이 아니기에 명시적으로 해제해야 합니다.
외부 자원의 종류
- 파일 핸들 (FileInputStream, FileReader 등)
- 네트워크 소켓 (Socket, ServerSocket 등)
- JDBC 커넥션, Statement, ResultSet
- 쓰레드 풀, Timer, ExecutorService
- NIO 버퍼, 채널 등
2). Java 플랫폼과 운영체제 종속 자원 사이의 간극
- Java는 플랫폼 독립적인 언어이지만, 실제로 다루는 자원들은 대부분 운영체제에 종속되어 있습니다다.
- 파일 핸들: OS의 파일 디스크 자원
- DB 커넥션: DB 서버와의 TCP 커넥션
- 스레드: OS 쓰레드 풀 자원
- 이 자원들은 운영체제 수준에서 개수 제한이 있고, 점유 시간이 길수록 시스템 안정성 저하로 이어질 수 있습니다.
📌 파일 핸들을 닫지 않으면 java.io.FileNotFoundException: Too many open files
📌 DB 커넥션을 반환하지 않으면 Timeout waiting for connection from pool
※ 락(lock) 사용 경험이 있다면, 자원 점유 시간이 길어졌을 때 발생할 수 있는 시스템 안정성 문제(예: 데드락)를 고려해본 적이 있을 것입니다.
3). 장시간 실행되는 서버 애플리케이션 특성
- Java는 실행 환경이 장시간 이어기지는 환경(웹서버, API 게이트웨이, 배치 처리, 메시징 서버 등)으로 자원 누수가 누적되면 시스템 장애로 직결됩니다.
4). 멀티스레드 환경에서의 자원 정리 복잡성
- Java는 멀티스레드 처리를 지원하기 때문에, 자원을 여러 쓰레드에서 공유하는 구조가 일반적입니다. 이때 하나의 쓰레드에서 예외가 발생해 자원이 닫히지 않으면, 다른 쓰레드까지 영향을 받을 수 있습니다. 따라서 병렬 실행 환경에서는 자원 해제 로직이 반드시 필요합니다.
2. 이전 Java에서 자원관리 방법과 그 차이점
try-finally 방식(Java 6 이하) | try-with-resources 방식 (Java 7 이상) | |
자원 해제 | 수동 | 자동 |
예외 관리 | 원본 예외 정보가 사라질 수 있음. | 원래 예외와 함께 전달 |
코드 가독성 | 중첩 try-catch 작성으로 가독성 낮음. | 선언부에 자원을 나열하므로, boilerplate 코드가 줄어들어 상대적으로 가독성 높음. |
지원 범위 | 제약 없음 | AutoCloseable 인터페이스 구현체만 사용 가능 |
블록 스코프 | try 블록 외의 변수도 참조 가능 | try 블록 내의 변수만 참조 가능 |
1) try-finally 방식 (Java 6 이하)
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// 처리 로직
} catch (IOException e) {
// 예외 처리
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// close() 중 발생한 예외 처리
}
}
}
- close() 중 예외가 발생하면 원래 발생한 예외가 덮여집니다. (suppressed exception 손실)
2) try-with-resources 방식
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
// 처리 로직
} catch (IOException e) {
// 예외 처리
}
- AutoCloseable 또는 Closeable 구현체만 사용할 수 있습니다.
- try 블록이 끝날 때 자원이 자동으로 close() 호출됩니다.
기존에 Closeable 구현체를 try–finally로도 관리할 수 있었지만, 자동 자원 해제 외에 어떤 기능이 필요했을까?
AutoCloseable 과 Closeable 의 차이
Closeable | AutoCloseable | |
자원 관리 | I/O 스트림 대상(IOExcetion 한정) | 모든 종류의 외부 자원(Exception) (데이터베이스 커넥션, 스레드 풀, 사용자 정의 리소스 등) |
예외 서명 | Closeable.close() throws IOException | AutoCloseable.close() throws Exception |
- 자원 관리 범위에 차이가 있지만 Closeable은 AutoCloseable의 하위 인터페이스이기 때문에, Close 구현체도 당연히 자동으로 close()가 호출됩니다.
- 즉, 두 인터페이스 예외 서명과 관리 범위만 다를 뿐, 자동 자원 해제 측면에서는 동일하게 작동합니다.
AutoCloseable 한계점
- AutoCloseable 구현체만 지원합니다. 다른 유형의 자원에는 적용할 수 없습니다.
→ implements AutoCloseable을 해야만 자동 해제 대상이 됩니다. - 리소스 변수는 try 헤더 안에서만 유효합니다. 블록 외부에서 재사용하기 어렵습니다.
→ finally 바깥이나 다른 블록에서 재사용하려면 수동 선언·해제 방식이 필요합니다. - suppressed 예외를 명시적으로 꺼내 처리하지 않으면 누락될 수 있습니다.
→ 자동 해제는 되지만, getSuppressed()를 호출해야만 close() 중 발생한 예외를 확인할 수 있습니다. - 복잡한 해제 로직(조건부 또는 순서 제어 등)을 구현할 때는 여전히 수동 try–finally가 필요합니다.
→ close 순서를 상황별로 제어하거나, 특정 조건일 때만 해제하려면 기존 방식이 더 유연합니다.
결론
Java는 GC의 한계, 운영체제 종속성, 장시간 실행되는 서버 환경, 멀티스레드 특성 등으로 자원 관리가 매우 중요하며, Java 7에서 안전한 자원 관리를 위해 try-with-resources 문법이 추가되었습니다. 이 문법은 try 블록이 종료될 때 해당 자원이 AutoCloseable 인터페이스를 구현하고 있으면 자동으로 close() 메서드를 호출해 외부 자원 등 모든 자원을 해제합니다.
참고
https://f-lab.kr/insight/java-resource-management-20241209
https://mangkyu.tistory.com/217